From 9fba18b247bcfdaf47111f6770541e775725f484 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Mon, 25 Apr 2022 21:42:03 -0400 Subject: [PATCH 001/182] move dynamic code into a separate project --- AsmResolver.sln | 30 ++++++ .../AsmResolver.DotNet.Dynamic.csproj | 33 ++++++ .../DynamicCilOperandResolver.cs | 13 +-- .../DynamicMethodDefinition.cs | 101 ++++++++++++++++++ .../DynamicMethodHelper.cs | 11 +- .../AsmResolver.DotNet.csproj | 1 + .../Code/Cil/CilMethodBody.cs | 37 ------- .../DynamicMethodDefinition.cs | 62 ----------- .../Properties/AssemblyInfo.cs | 3 + src/AsmResolver.DotNet/ReferenceImporter.cs | 3 + .../AsmResolver.DotNet.Dynamic.Tests.csproj | 26 +++++ .../DynamicMethodDefinitionTest.cs | 33 +++++- .../Code/Cil/CilMethodBodyTest.cs | 27 ----- 13 files changed, 239 insertions(+), 141 deletions(-) create mode 100644 src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj rename src/{AsmResolver.DotNet/Code/Cil => AsmResolver.DotNet.Dynamic}/DynamicCilOperandResolver.cs (92%) create mode 100644 src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs rename src/{AsmResolver.DotNet => AsmResolver.DotNet.Dynamic}/DynamicMethodHelper.cs (93%) delete mode 100644 src/AsmResolver.DotNet/DynamicMethodDefinition.cs create mode 100644 src/AsmResolver.DotNet/Properties/AssemblyInfo.cs create mode 100644 test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj rename test/{AsmResolver.DotNet.Tests => AsmResolver.DotNet.Dynamic.Tests}/DynamicMethodDefinitionTest.cs (70%) diff --git a/AsmResolver.sln b/AsmResolver.sln index 526ac85f6..5699c731f 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -84,6 +84,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.DotNet.Dynamic.Tests", "test\AsmResolver.DotNet.Dynamic.Tests\AsmResolver.DotNet.Dynamic.Tests.csproj", "{C089D0AB-B428-4136-89CC-7974CB590513}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsmResolver.DotNet.Dynamic", "src\AsmResolver.DotNet.Dynamic\AsmResolver.DotNet.Dynamic.csproj", "{62420213-67AD-40FC-B451-BD05C2437CE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -422,6 +426,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 + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x64.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x64.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x86.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x86.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|Any CPU.Build.0 = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x64.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x64.Build.0 = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x86.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x86.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x64.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x64.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x86.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|Any CPU.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x64.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x64.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x86.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -459,6 +487,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} + {C089D0AB-B428-4136-89CC-7974CB590513} = {786C1732-8C96-45DD-97BB-639C9AA7F45B} + {62420213-67AD-40FC-B451-BD05C2437CE3} = {34A95168-A162-4F6A-803B-B6F221FE9EA6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3302AC79-6D23-4E7D-8C5F-C0C7261044D0} diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj new file mode 100644 index 000000000..990deaf62 --- /dev/null +++ b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj @@ -0,0 +1,33 @@ + + + + AsmResolver.DotNet.Dynamic + Dynamic method support for the AsmResolver executable file inspection toolsuite. + exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly dynamic + true + 1701;1702;NU5105 + net6.0;netcoreapp3.1;netstandard2.0 + enable + + + + bin\Debug\AsmResolver.DotNet.xml + + + + bin\Release\AsmResolver.DotNet.xml + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs similarity index 92% rename from src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs rename to src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs index d3244f8e2..6cfa15b76 100644 --- a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; using System.Reflection; +using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; -namespace AsmResolver.DotNet.Code.Cil +namespace AsmResolver.DotNet.Dynamic { /// /// Provides an implementation of that resolves operands based on @@ -34,13 +35,13 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe switch (token.Table) { case TableIndex.TypeDef: - object? type = _tokens[(int) token.Rid]; + object? type = _tokens[(int)token.Rid]; if (type is RuntimeTypeHandle runtimeTypeHandle) return _importer.ImportType(Type.GetTypeFromHandle(runtimeTypeHandle)); break; case TableIndex.Field: - object? field = _tokens[(int) token.Rid]; + object? field = _tokens[(int)token.Rid]; if (field is null) return null; @@ -61,7 +62,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe case TableIndex.Method: case TableIndex.MemberRef: - object? obj = _tokens[(int) token.Rid]; + object? obj = _tokens[(int)token.Rid]; if (obj is null) return null; @@ -94,7 +95,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe break; case TableIndex.StandAloneSig: - var reader = ByteArrayDataSource.CreateReader((byte[]) _tokens[(int) token.Rid]!); + var reader = ByteArrayDataSource.CreateReader((byte[])_tokens[(int)token.Rid]!); return CallingConventionSignature.FromReader(new BlobReadContext(_readerContext), ref reader); } @@ -104,7 +105,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe /// public override object? ResolveString(MetadataToken token) { - return _tokens[(int) token.Rid] as string; + return _tokens[(int)token.Rid] as string; } } } diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs new file mode 100644 index 000000000..9e216ebea --- /dev/null +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Serialized; +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; +using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; + +namespace AsmResolver.DotNet.Dynamic +{ + /// + /// Represents a single method in a type definition of a .NET module. + /// + public class DynamicMethodDefinition : MethodDefinition + { + /// + /// Create a Dynamic Method Definition + /// + /// Target Module + /// Dynamic Method / Delegate / DynamicResolver + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver and FromDynamicMethod")] + public DynamicMethodDefinition(ModuleDefinition module, object dynamicMethodObj) : + base(new MetadataToken(TableIndex.Method, 0)) + { + dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); + var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method"); + if (methodBase is null) + { + throw new ArgumentException( + "Could not get the underlying method base in the provided dynamic method object."); + } + + Module = module; + Name = methodBase.Name; + Attributes = (MethodAttributes)methodBase.Attributes; + Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase, module)); + CilMethodBody = ToCilMethodBody(this, dynamicMethodObj); + } + + private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition module) + { + var imp = new ReferenceImporter(module); + var returnType = methodBase is MethodInfo info + ? imp.ImportTypeSignature(info.ReturnType) + : module.CorLibTypeFactory.Void; + + var parameters = methodBase.GetParameters(); + + var parameterTypes = new TypeSignature[parameters.Length]; + for (int i = 0; i < parameterTypes.Length; i++) + parameterTypes[i] = imp.ImportTypeSignature(parameters[i].ParameterType); + + return new MethodSignature( + methodBase.IsStatic ? 0 : CallingConventionAttributes.HasThis, + returnType, parameterTypes); + } + + /// + public override ModuleDefinition Module { get; } + + /// + /// Creates a CIL method body from a dynamic method. + /// + /// The method that owns the method body. + /// The Dynamic Method/Delegate/DynamicResolver. + /// The method body. + public static CilMethodBody ToCilMethodBody(MethodDefinition method, object dynamicMethodObj) + { + if (!(method.Module is SerializedModuleDefinition module)) + throw new ArgumentException("Method body should reference a serialized module."); + + var result = new CilMethodBody(method); + dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); + + //Get Runtime Fields + byte[] code = FieldReader.ReadField(dynamicMethodObj, "m_code")!; + object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; + var tokenList = FieldReader.ReadField>(scope, "m_tokens")!; + byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!; + byte[] ehHeader = FieldReader.ReadField(dynamicMethodObj, "m_exceptionHeader")!; + var ehInfos = FieldReader.ReadField>(dynamicMethodObj, "m_exceptions")!; + + //Local Variables + DynamicMethodHelper.ReadLocalVariables(result, method, localSig); + + // Read raw instructions. + var reader = ByteArrayDataSource.CreateReader(code); + var disassembler = new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList)); + result.Instructions.AddRange(disassembler.ReadInstructions()); + + //Exception Handlers + DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, new ReferenceImporter(module)); + + return result; + } + } +} diff --git a/src/AsmResolver.DotNet/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs similarity index 93% rename from src/AsmResolver.DotNet/DynamicMethodHelper.cs rename to src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index f3cece5db..283a2b7b5 100644 --- a/src/AsmResolver.DotNet/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -9,7 +9,7 @@ using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; -namespace AsmResolver.DotNet +namespace AsmResolver.DotNet.Dynamic { internal static class DynamicMethodHelper { @@ -34,10 +34,10 @@ public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition IList? ehInfos, byte[] ehHeader, ReferenceImporter importer) { //Sample needed! - if (ehHeader is {Length: > 4}) + if (ehHeader is { Length: > 4 }) throw new NotImplementedException("Exception handlers from ehHeader not supported yet."); - if (ehInfos is {Count: > 0}) + if (ehInfos is { Count: > 0 }) { foreach (var ehInfo in ehInfos) InterpretEHInfo(methodBody, importer, ehInfo); @@ -61,7 +61,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter int handlerStart = FieldReader.ReadField(ehInfo, "m_catchAddr")![i]; int handlerEnd = FieldReader.ReadField(ehInfo, "m_catchEndAddr")![i]; var exceptionType = FieldReader.ReadField(ehInfo, "m_catchClass")![i]; - var handlerType = (CilExceptionHandlerType) FieldReader.ReadField(ehInfo, "m_type")![i]; + var handlerType = (CilExceptionHandlerType)FieldReader.ReadField(ehInfo, "m_type")![i]; var endTryLabel = instructions.GetByOffset(tryEnd)?.CreateLabel() ?? new CilOffsetLabel(tryEnd); @@ -81,7 +81,6 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter } } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls GetTypes")] public static object ResolveDynamicResolver(object dynamicMethodObj) { //Convert dynamicMethodObj to DynamicResolver @@ -111,7 +110,7 @@ public static object ResolveDynamicResolver(object dynamicMethodObj) .Invoke(dynamicMethodObj, null); //Create instance of dynamicResolver - dynamicMethodObj = Activator.CreateInstance(dynamicResolver, (BindingFlags) (-1), null, new[] + dynamicMethodObj = Activator.CreateInstance(dynamicResolver, (BindingFlags)(-1), null, new[] { ilGenerator }, null)!; diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index c041214b1..490e53fe7 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -8,6 +8,7 @@ 1701;1702;NU5105 enable net6.0;netcoreapp3.1;netstandard2.0 + true diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs index 180e474f8..e1915c5e6 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs @@ -130,43 +130,6 @@ public bool VerifyLabelsOnBuild | (value ? CilMethodBodyBuildFlags.VerifyLabels : 0); } - /// - /// Creates a CIL method body from a dynamic method. - /// - /// The method that owns the method body. - /// The Dynamic Method/Delegate/DynamicResolver. - /// The method body. - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver")] - public static CilMethodBody FromDynamicMethod(MethodDefinition method, object dynamicMethodObj) - { - if (!(method.Module is SerializedModuleDefinition module)) - throw new ArgumentException("Method body should reference a serialized module."); - - var result = new CilMethodBody(method); - dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); - - //Get Runtime Fields - byte[] code = FieldReader.ReadField(dynamicMethodObj, "m_code")!; - object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; - var tokenList = FieldReader.ReadField>(scope, "m_tokens")!; - byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!; - byte[] ehHeader = FieldReader.ReadField(dynamicMethodObj, "m_exceptionHeader")!; - var ehInfos = FieldReader.ReadField>(dynamicMethodObj, "m_exceptions")!; - - //Local Variables - DynamicMethodHelper.ReadLocalVariables(result, method, localSig); - - // Read raw instructions. - var reader = ByteArrayDataSource.CreateReader(code); - var disassembler = new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList)); - result.Instructions.AddRange(disassembler.ReadInstructions()); - - //Exception Handlers - DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, new ReferenceImporter(module)); - - return result; - } - /// /// Creates a CIL method body from a raw CIL method body. /// diff --git a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet/DynamicMethodDefinition.cs deleted file mode 100644 index b95d8f003..000000000 --- a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Reflection; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Signatures; -using AsmResolver.DotNet.Signatures.Types; -using AsmResolver.PE.DotNet.Metadata.Tables; -using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; - -namespace AsmResolver.DotNet -{ - /// - /// Represents a single method in a type definition of a .NET module. - /// - public class DynamicMethodDefinition : MethodDefinition - { - /// - /// Create a Dynamic Method Definition - /// - /// Target Module - /// Dynamic Method / Delegate / DynamicResolver - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver and FromDynamicMethod")] - public DynamicMethodDefinition(ModuleDefinition module,object dynamicMethodObj) : - base(new MetadataToken(TableIndex.Method, 0)) - { - dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); - var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method"); - if (methodBase is null) - { - throw new ArgumentException( - "Could not get the underlying method base in the provided dynamic method object."); - } - - Module = module; - Name = methodBase.Name; - Attributes = (MethodAttributes)methodBase.Attributes; - Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase,module)); - CilMethodBody = CilMethodBody.FromDynamicMethod(this,dynamicMethodObj); - } - - private MethodSignature ResolveSig(MethodBase methodBase,ModuleDefinition module) - { - var imp = new ReferenceImporter(module); - var returnType = methodBase is MethodInfo info - ? imp.ImportTypeSignature(info.ReturnType) - : module.CorLibTypeFactory.Void; - - var parameters = methodBase.GetParameters(); - - var parameterTypes = new TypeSignature[parameters.Length]; - for (int i = 0; i < parameterTypes.Length; i++) - parameterTypes[i] = imp.ImportTypeSignature(parameters[i].ParameterType); - - return new MethodSignature( - methodBase.IsStatic ? 0 : CallingConventionAttributes.HasThis, - returnType, parameterTypes); - } - - /// - public override ModuleDefinition Module { get; } - - } -} diff --git a/src/AsmResolver.DotNet/Properties/AssemblyInfo.cs b/src/AsmResolver.DotNet/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d2c0f49fa --- /dev/null +++ b/src/AsmResolver.DotNet/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("AsmResolver.DotNet.Dynamic")] diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 8af788d7d..daef76fc7 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -425,6 +425,7 @@ public virtual MethodSpecification ImportMethod(MethodSpecification method) /// /// The method to import. /// The imported method. + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveMethod")] public virtual IMethodDescriptor ImportMethod(MethodBase method) { if (method is null) @@ -455,6 +456,7 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method) return new MemberReference(ImportType(method.DeclaringType), method.Name, result); } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ImportMethod")] private IMethodDescriptor ImportGenericMethod(MethodInfo method) { var memberRef = (IMethodDefOrRef) ImportMethod(method.GetGenericMethodDefinition()); @@ -509,6 +511,7 @@ public FieldSignature ImportFieldSignature(FieldSignature signature) /// The field to import. /// The imported field. /// Occurs when a field is not added to a type. + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveField")] public MemberReference ImportField(FieldInfo field) { if (field is null) diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj new file mode 100644 index 000000000..9a814bd0f --- /dev/null +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + disable + + false + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs similarity index 70% rename from test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs rename to test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index f8f203513..7c029efc1 100644 --- a/test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using AsmResolver.DotNet.Signatures.Types; @@ -6,7 +6,7 @@ using AsmResolver.PE.DotNet.Cil; using Xunit; -namespace AsmResolver.DotNet.Tests +namespace AsmResolver.DotNet.Dynamic.Tests { public class DynamicMethodDefinitionTest { @@ -65,5 +65,32 @@ public void RtDynamicMethod() module.CorLibTypeFactory.Int32 }); } + + [Fact] + public void ReadDynamicMethodToCilMethodBody() + { + var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); + + var type = module.TopLevelTypes.First(t => t.Name == nameof(TDynamicMethod)); + + var method = type.Methods.FirstOrDefault(m => m.Name == nameof(TDynamicMethod.GenerateDynamicMethod)); + + DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); + + //Dynamic method => CilMethodBody + var body = DynamicMethodDefinition.ToCilMethodBody(method, generateDynamicMethod); + + Assert.NotNull(body); + + Assert.NotEmpty(body.Instructions); + + Assert.Equal(body.Instructions.Select(q => q.OpCode), new CilOpCode[] + { + CilOpCodes.Ldarg_0, + CilOpCodes.Call, + CilOpCodes.Ldarg_1, + CilOpCodes.Ret + }); + } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs index 8dda5be9e..f13198d60 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs @@ -124,33 +124,6 @@ public void ReadFatMethodWithExceptionHandler() Assert.Single(body.ExceptionHandlers); } - [Fact] - public void ReadDynamicMethod() - { - var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); - - var type = module.TopLevelTypes.First(t => t.Name == nameof(TDynamicMethod)); - - var method = type.Methods.FirstOrDefault(m => m.Name == nameof(TDynamicMethod.GenerateDynamicMethod)); - - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - //Dynamic method => CilMethodBody - var body = CilMethodBody.FromDynamicMethod(method, generateDynamicMethod); - - Assert.NotNull(body); - - Assert.NotEmpty(body.Instructions); - - Assert.Equal(body.Instructions.Select(q=>q.OpCode),new CilOpCode[] - { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - } - private static CilMethodBody CreateDummyBody(bool isVoid) { var module = new ModuleDefinition("DummyModule"); From 507ab5485234ac4bb912da7724c0a0c070b7d210 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Tue, 26 Apr 2022 11:11:26 -0400 Subject: [PATCH 002/182] move FieldReader to dynamic project --- src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs | 1 - .../FieldReader.cs | 4 ++-- src/AsmResolver.DotNet/Properties/AssemblyInfo.cs | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) rename src/{AsmResolver.DotNet => AsmResolver.DotNet.Dynamic}/FieldReader.cs (96%) delete mode 100644 src/AsmResolver.DotNet/Properties/AssemblyInfo.cs diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index 9e216ebea..b58062490 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -22,7 +22,6 @@ 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/FieldReader.cs b/src/AsmResolver.DotNet.Dynamic/FieldReader.cs similarity index 96% rename from src/AsmResolver.DotNet/FieldReader.cs rename to src/AsmResolver.DotNet.Dynamic/FieldReader.cs index 8133f1e63..40e4a4450 100644 --- a/src/AsmResolver.DotNet/FieldReader.cs +++ b/src/AsmResolver.DotNet.Dynamic/FieldReader.cs @@ -1,6 +1,6 @@ -using System.Reflection; +using System.Reflection; -namespace AsmResolver.DotNet +namespace AsmResolver.DotNet.Dynamic { internal static class FieldReader { diff --git a/src/AsmResolver.DotNet/Properties/AssemblyInfo.cs b/src/AsmResolver.DotNet/Properties/AssemblyInfo.cs deleted file mode 100644 index d2c0f49fa..000000000 --- a/src/AsmResolver.DotNet/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("AsmResolver.DotNet.Dynamic")] From 43d1aa5c92d30f9bdb608944c37b259fdb512c0f Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Tue, 26 Apr 2022 11:22:12 -0400 Subject: [PATCH 003/182] more descriptive trim warnings --- src/AsmResolver.DotNet/ReferenceImporter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index daef76fc7..177d09396 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -425,7 +425,7 @@ public virtual MethodSpecification ImportMethod(MethodSpecification method) /// /// The method to import. /// The imported method. - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveMethod")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls System.Reflection.Module.ResolveMethod(int)")] public virtual IMethodDescriptor ImportMethod(MethodBase method) { if (method is null) @@ -456,7 +456,7 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method) return new MemberReference(ImportType(method.DeclaringType), method.Name, result); } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ImportMethod")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls AsmResolver.DotNet.ReferenceImporter.ImportMethod(System.Reflection.MethodBase)")] private IMethodDescriptor ImportGenericMethod(MethodInfo method) { var memberRef = (IMethodDefOrRef) ImportMethod(method.GetGenericMethodDefinition()); @@ -511,7 +511,7 @@ public FieldSignature ImportFieldSignature(FieldSignature signature) /// The field to import. /// The imported field. /// Occurs when a field is not added to a type. - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveField")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls System.Reflection.Module.ResolveField(int)")] public MemberReference ImportField(FieldInfo field) { if (field is null) From 9e404cbf68c03e7386b63bee3f18e682999897c5 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Tue, 26 Apr 2022 12:18:05 -0400 Subject: [PATCH 004/182] make dynamic method body creation private --- .../DynamicMethodDefinition.cs | 6 ++--- .../DynamicMethodDefinitionTest.cs | 27 ------------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index b58062490..4349d9006 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -37,7 +37,7 @@ public class DynamicMethodDefinition : MethodDefinition Name = methodBase.Name; Attributes = (MethodAttributes)methodBase.Attributes; Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase, module)); - CilMethodBody = ToCilMethodBody(this, dynamicMethodObj); + CilMethodBody = CreateDynamicMethodBody(this, dynamicMethodObj); } private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition module) @@ -62,12 +62,12 @@ private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition modul public override ModuleDefinition Module { get; } /// - /// Creates a CIL method body from a dynamic method. + /// Creates a CIL method body from a dynamic method. /// /// The method that owns the method body. /// The Dynamic Method/Delegate/DynamicResolver. /// The method body. - public static CilMethodBody ToCilMethodBody(MethodDefinition method, object dynamicMethodObj) + private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, object dynamicMethodObj) { if (!(method.Module is SerializedModuleDefinition module)) throw new ArgumentException("Method body should reference a serialized module."); diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index 7c029efc1..6d889cb60 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -65,32 +65,5 @@ public void RtDynamicMethod() module.CorLibTypeFactory.Int32 }); } - - [Fact] - public void ReadDynamicMethodToCilMethodBody() - { - var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); - - var type = module.TopLevelTypes.First(t => t.Name == nameof(TDynamicMethod)); - - var method = type.Methods.FirstOrDefault(m => m.Name == nameof(TDynamicMethod.GenerateDynamicMethod)); - - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - //Dynamic method => CilMethodBody - var body = DynamicMethodDefinition.ToCilMethodBody(method, generateDynamicMethod); - - Assert.NotNull(body); - - Assert.NotEmpty(body.Instructions); - - Assert.Equal(body.Instructions.Select(q => q.OpCode), new CilOpCode[] - { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - } } } From 84a79bf8b0ec0eb0cb47249e1a050fc7546df9ac Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 20:01:53 +0200 Subject: [PATCH 005/182] 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 006/182] 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 007/182] 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 008/182] 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 009/182] 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 010/182] 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 011/182] 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 012/182] 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 013/182] 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 b5d0ae3ce242c77af91be61e66fba728be0faa29 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 26 May 2022 15:09:12 +0200 Subject: [PATCH 014/182] 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 015/182] 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 016/182] 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 017/182] 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 018/182] 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^ 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

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 019/182] 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 020/182] 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 021/182] 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 022/182] 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 640403dc73476dc6a2b3bd707bf9f7dca7910f7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jun 2022 14:39:13 +0000 Subject: [PATCH 023/182] Bump System.Text.Json from 6.0.4 to 6.0.5 Bumps [System.Text.Json](https://github.com/dotnet/runtime) from 6.0.4 to 6.0.5. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v6.0.4...v6.0.5) --- 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 456ce3119..0fe43ce47 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 a4d119417bbe0b6be693858ff25cf6d3e84eb468 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 16 Jun 2022 17:28:36 +0200 Subject: [PATCH 024/182] 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 025/182] 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 026/182] 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 027/182] 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 028/182] 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 029/182] 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 030/182] 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 031/182] 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 032/182] 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 033/182] 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 034/182] 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 035/182] 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 036/182] 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 037/182] 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 038/182] 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 039/182] 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 040/182] 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 041/182] 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 5419a4694bc5f854400f26c5ed5195226d941a44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 14:57:59 +0000 Subject: [PATCH 042/182] Bump Nullable from 1.3.0 to 1.3.1 Bumps [Nullable](https://github.com/manuelroemer/Nullable) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/manuelroemer/Nullable/releases) - [Changelog](https://github.com/manuelroemer/Nullable/blob/master/CHANGELOG.md) - [Commits](https://github.com/manuelroemer/Nullable/commits) --- updated-dependencies: - dependency-name: Nullable dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 2 +- src/AsmResolver.PE.File/AsmResolver.PE.File.csproj | 2 +- src/AsmResolver.PE/AsmResolver.PE.csproj | 2 +- src/AsmResolver/AsmResolver.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 456ce3119..009af4c38 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -23,7 +23,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index 5c0bfda0f..98b14d222 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -25,7 +25,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index 058547040..a008308e1 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index 07dbf2281..b7685bc8a 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 7426481edc5b97af9eaa5677d08198bc9d1d8747 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 15 Jul 2022 21:26:23 +0200 Subject: [PATCH 043/182] Basic PDB symbol interpretation. --- src/AsmResolver.Symbols.Pdb/PdbImage.cs | 72 ++ .../Records/SymbolRecord.cs | 35 + .../Records/SymbolType.cs | 865 ++++++++++++++++++ .../Records/UnknownSymbol.cs | 35 + .../SerializedPdbImage.cs | 55 ++ 5 files changed, 1062 insertions(+) create mode 100644 src/AsmResolver.Symbols.Pdb/PdbImage.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs create mode 100644 src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs new file mode 100644 index 000000000..59fe4f361 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Threading; +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Msf; +using AsmResolver.Symbols.Pdb.Records; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Represents a single Program Debug Database (PDB) image. +/// +public class PdbImage +{ + private IList? _symbols; + + /// + /// Gets a collection of all symbols stored in the PDB image. + /// + public IList Symbols + { + get + { + if (_symbols is null) + Interlocked.CompareExchange(ref _symbols, GetSymbols(), null); + return _symbols; + } + } + + /// + /// Reads a PDB image from the provided input file. + /// + /// The path to the PDB file. + /// The read PDB image. + public static PdbImage FromFile(string path) => FromFile(MsfFile.FromFile(path)); + + /// + /// Reads a PDB image from the provided input file. + /// + /// The input file. + /// The read PDB image. + public static PdbImage FromFile(IInputFile file) => FromFile(MsfFile.FromFile(file)); + + /// + /// Interprets a byte array as a PDB image. + /// + /// The data to interpret. + /// The read PDB image. + public static PdbImage FromBytes(byte[] data) => FromFile(MsfFile.FromBytes(data)); + + /// + /// Reads an PDB image from the provided input stream reader. + /// + /// The reader. + /// The read PDB image. + public static PdbImage FromReader(BinaryStreamReader reader) => FromFile(MsfFile.FromReader(reader)); + + /// + /// Loads a PDB image from the provided MSF file. + /// + /// The MSF file. + /// The read PDB image. + public static PdbImage FromFile(MsfFile file) => new SerializedPdbImage(file); + + /// + /// Obtains a collection of symbols stored in the symbol record stream of the PDB image. + /// + /// The symbols. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSymbols() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs b/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs new file mode 100644 index 000000000..f790e0c4c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a single symbol record within the symbol record stream of a PDB file. +/// +public abstract class SymbolRecord +{ + /// + /// Gets the type of symbol this record encodes. + /// + public abstract SymbolType SymbolType + { + get; + } + + /// + /// Reads a single symbol record from the input stream. + /// + /// The input stream. + /// The read symbol. + public static SymbolRecord FromReader(ref BinaryStreamReader reader) + { + ushort length = reader.ReadUInt16(); + var type = (SymbolType) reader.ReadUInt16(); + var dataReader = reader.ForkRelative(reader.RelativeOffset, (uint) (length - 2)); + reader.Offset += (ulong) (length - 2); + + return type switch + { + _ => new UnknownSymbol(type, dataReader.ReadToEnd()) + }; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs b/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs new file mode 100644 index 000000000..dc72b2b72 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs @@ -0,0 +1,865 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +// Reference: cvinfo.h + +/// +/// Provides members defining all symbol record types that can be stored in a PDB symbol stream. +/// +public enum SymbolType : ushort +{ +#pragma warning disable CS1591 + /// + /// Indicates the symbol is a Compile flags symbol + /// + Compile = 0x0001, + + /// + /// Indicates the symbol is a Register variable + /// + Register16T = 0x0002, + + /// + /// Indicates the symbol is a constant symbol + /// + Constant16T = 0x0003, + + /// + /// Indicates the symbol is a User defined type + /// + Udt16T = 0x0004, + + /// + /// Indicates the symbol is a Start Search + /// + SSearch = 0x0005, + + /// + /// Indicates the symbol is a Block, procedure, "with" or thunk end + /// + End = 0x0006, + + /// + /// Indicates the symbol is a Reserve symbol space in $$Symbols table + /// + Skip = 0x0007, + + /// + /// Indicates the symbol is a Reserved symbol for CV internal use + /// + CVReserve = 0x0008, + + /// + /// Indicates the symbol is a path to object file name + /// + ObjnameSt = 0x0009, + + /// + /// Indicates the symbol is a end of argument/return list + /// + EndArg = 0x000a, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdt16T = 0x000b, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg16T = 0x000c, + + /// + /// Indicates the symbol is a return description symbol + /// + Return = 0x000d, + + /// + /// Indicates the symbol is a description of this pointer on entry + /// + EntryThis = 0x000e, + + /// + /// Indicates the symbol is a BP-relative + /// + BPRel16 = 0x0100, + + /// + /// Indicates the symbol is a Module-local symbol + /// + Ldata16 = 0x0101, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData16 = 0x0102, + + /// + /// Indicates the symbol is a a public symbol + /// + Pub16 = 0x0103, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc16 = 0x0104, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc16 = 0x0105, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk16 = 0x0106, + + /// + /// Indicates the symbol is a block start + /// + Block16 = 0x0107, + + /// + /// Indicates the symbol is a with start + /// + With16 = 0x0108, + + /// + /// Indicates the symbol is a code label + /// + Label16 = 0x0109, + + /// + /// Indicates the symbol is a change execution model + /// + CexModel16 = 0x010a, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable16 = 0x010b, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel16 = 0x010c, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel3216T = 0x0200, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData3216T = 0x0201, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData3216T = 0x0202, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub3216T = 0x0203, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc3216T = 0x0204, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc3216T = 0x0205, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk32St = 0x0206, + + /// + /// Indicates the symbol is a block start + /// + Block32St = 0x0207, + + /// + /// Indicates the symbol is a with start + /// + With32St = 0x0208, + + /// + /// Indicates the symbol is a code label + /// + Label32St = 0x0209, + + /// + /// Indicates the symbol is a change execution model + /// + CexModel32 = 0x020a, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable3216T = 0x020b, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel3216T = 0x020c, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread3216T = 0x020d, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread3216T = 0x020e, + + /// + /// Indicates the symbol is a static link for MIPS EH implementation + /// + SLink32 = 0x020f, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMip16T = 0x0300, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMip16T = 0x0301, + + // if these ref symbols have names following then the names are in ST format + /// + /// Indicates the symbol is a Reference to a procedure + /// + ProcRefSt = 0x0400, + + /// + /// Indicates the symbol is a Reference to data + /// + DataRefSt = 0x0401, + + /// + /// Indicates the symbol is a Used for page alignment of symbols + /// + Align = 0x0402, + + /// + /// Indicates the symbol is a Local Reference to a procedure + /// + LProcRefSt = 0x0403, + + /// + /// Indicates the symbol is a OEM defined symbol + /// + Oem = 0x0404, + + // sym records with 32-bit types embedded instead of 16-bit + // all have 0x1000 bit set for easy identification + // only do the 32-bit target versions since we don't really + // care about 16-bit ones anymore. + // TI16_MAX = 0x1000, + + /// + /// Indicates the symbol is a Register variable + /// + RegisterSt = 0x1001, + + /// + /// Indicates the symbol is a constant symbol + /// + ConstantSt = 0x1002, + + /// + /// Indicates the symbol is a User defined type + /// + UdtSt = 0x1003, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdtSt = 0x1004, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyRegSt = 0x1005, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel32St = 0x1006, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData32St = 0x1007, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData32St = 0x1008, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub32St = 0x1009, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc32St = 0x100a, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc32St = 0x100b, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable32 = 0x100c, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel32St = 0x100d, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread32St = 0x100e, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread32St = 0x100f, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMipSt = 0x1010, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMipSt = 0x1011, + + /// + /// Indicates the symbol is a extra frame and proc information + /// + FrameProc = 0x1012, + + /// + /// Indicates the symbol is a extended compile flags and info + /// + Compile2St = 0x1013, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg2St = 0x1014, + + /// + /// Indicates the symbol is a Local procedure start (IA64) + /// + LProcIa64St = 0x1015, + + /// + /// Indicates the symbol is a Global procedure start (IA64) + /// + GProcIa64St = 0x1016, + + /// + /// Indicates the symbol is a local IL sym with field for local slot index + /// + LocalSlotSt = 0x1017, + + /// + /// Indicates the symbol is a local IL sym with field for parameter slot index + /// + ParamSlotSt = 0x1018, + + /// + /// Indicates the symbol is a Annotation string literals + /// + Annotation = 0x1019, + + /// + /// Indicates the symbol is a Global proc + /// + GManProcSt = 0x101a, + + /// + /// Indicates the symbol is a Local proc + /// + LManProcSt = 0x101b, + + /// + /// Reserved + /// + Reserved1 = 0x101c, + + /// + /// Reserved + /// + Reserved2 = 0x101d, + + /// + /// Reserved + /// + Reserved3 = 0x101e, + + /// + /// Reserved + /// + RESERVED4 = 0x101f, + + LManDataSt = 0x1020, + + GManDataSt = 0x1021, + + ManFrameRelSt = 0x1022, + + ManRegisterSt = 0x1023, + + ManSlotSt = 0x1024, + + ManManyRegSt = 0x1025, + + ManRegRelSt = 0x1026, + + ManManyReg2St = 0x1027, + + /// + /// Indicates the symbol is a Index for type referenced by name from metadata + /// + ManTypRef = 0x1028, + + /// + /// Indicates the symbol is a Using namespace + /// + UNamespaceSt = 0x1029, + + // Symbols w/ SZ name fields. All name fields contain utf8 encoded strings. + /// + /// Indicates the symbol is a starting point for SZ name symbols + /// + StMax = 0x1100, + + /// + /// Indicates the symbol is a path to object file name + /// + ObjName = 0x1101, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk32 = 0x1102, + + /// + /// Indicates the symbol is a block start + /// + Block32 = 0x1103, + + /// + /// Indicates the symbol is a with start + /// + With32 = 0x1104, + + /// + /// Indicates the symbol is a code label + /// + Label32 = 0x1105, + + /// + /// Indicates the symbol is a Register variable + /// + Register = 0x1106, + + /// + /// Indicates the symbol is a constant symbol + /// + Constant = 0x1107, + + /// + /// Indicates the symbol is a User defined type + /// + Udt = 0x1108, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdt = 0x1109, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg = 0x110a, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel32 = 0x110b, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData32 = 0x110c, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData32 = 0x110d, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub32 = 0x110e, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc32 = 0x110f, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc32 = 0x1110, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel32 = 0x1111, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread32 = 0x1112, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread32 = 0x1113, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMips = 0x1114, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMips = 0x1115, + + /// + /// Indicates the symbol is a extended compile flags and info + /// + Compile2 = 0x1116, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg2 = 0x1117, + + /// + /// Indicates the symbol is a Local procedure start (IA64) + /// + LprocIa64 = 0x1118, + + /// + /// Indicates the symbol is a Global procedure start (IA64) + /// + GProcIa64 = 0x1119, + + /// + /// Indicates the symbol is a local IL sym with field for local slot index + /// + LocalSlot = 0x111a, + + /// + /// Indicates the symbol is a alias for LOCALSLOT + /// + Slot = LocalSlot, + + /// + /// Indicates the symbol is a local IL sym with field for parameter slot index + /// + ParamSlot = 0x111b, + + // symbols to support managed code debugging + LManData = 0x111c, + GManData = 0x111d, + ManFrameRel = 0x111e, + ManRegister = 0x111f, + ManSlot = 0x1120, + ManManyReg = 0x1121, + ManRegRel = 0x1122, + ManManyReg2 = 0x1123, + + /// + /// Indicates the symbol is a Using namespace + /// + UNamespace = 0x1124, + + // ref symbols with name fields + /// + /// Indicates the symbol is a Reference to a procedure + /// + ProcRef = 0x1125, + + /// + /// Indicates the symbol is a Reference to data + /// + DataRef = 0x1126, + + /// + /// Indicates the symbol is a Local Reference to a procedure + /// + LProcRef = 0x1127, + + /// + /// Indicates the symbol is a Reference to an ANNOTATION symbol + /// + AnnotationRef = 0x1128, + + /// + /// Indicates the symbol is a Reference to one of the many MANPROCSYM's + /// + TokenRef = 0x1129, + + // continuation of managed symbols + /// + /// Indicates the symbol is a Global proc + /// + GManProc = 0x112a, + + /// + /// Indicates the symbol is a Local proc + /// + LManProc = 0x112b, + + // short, light-weight thunks + /// + /// Indicates the symbol is a trampoline thunks + /// + Trampoline = 0x112c, + + /// + /// Indicates the symbol is a constants with metadata type info + /// + ManConstant = 0x112d, + + // native attributed local/parms + /// + /// Indicates the symbol is a relative to virtual frame ptr + /// + AttrFrameRel = 0x112e, + + /// + /// Indicates the symbol is a stored in a register + /// + AttrRegister = 0x112f, + + /// + /// Indicates the symbol is a relative to register (alternate frame ptr) + /// + AttrRegRel = 0x1130, + + /// + /// Indicates the symbol is a stored in >1 register + /// + AttrManyReg = 0x1131, + + // Separated code (from the compiler) support + SepCode = 0x1132, + + /// + /// Indicates the symbol is a defines a local symbol in optimized code + /// + Local2005 = 0x1133, + + /// + /// Indicates the symbol is a defines a single range of addresses in which symbol can be evaluated + /// + DefRange2005 = 0x1134, + + /// + /// Indicates the symbol is a defines ranges of addresses in which symbol can be evaluated + /// + DefRange22005 = 0x1135, + + /// + /// Indicates the symbol is a A COFF section in a PE executable + /// + Section = 0x1136, + + /// + /// Indicates the symbol is a A COFF group + /// + CoffGroup = 0x1137, + + /// + /// Indicates the symbol is a A export + /// + Export = 0x1138, + + /// + /// Indicates the symbol is a Indirect call site information + /// + CallSiteInfo = 0x1139, + + /// + /// Indicates the symbol is a Security cookie information + /// + FrameCookie = 0x113a, + + /// + /// Indicates the symbol is a Discarded by LINK /OPT:REF (experimental, see richards) + /// + Discarded = 0x113b, + + /// + /// Indicates the symbol is a Replacement for COMPILE2 + /// + Compile3 = 0x113c, + + /// + /// Indicates the symbol is a Environment block split off from COMPILE2 + /// + EnvBlock = 0x113d, + + /// + /// Indicates the symbol is a defines a local symbol in optimized code + /// + Local = 0x113e, + + /// + /// Indicates the symbol is a defines a single range of addresses in which symbol can be evaluated + /// + DefRange = 0x113f, + + /// + /// Indicates the symbol is a ranges for a subfield + /// + DefRangeSubField = 0x1140, + + /// + /// Indicates the symbol is a ranges for en-registered symbol + /// + DefRangeRegister = 0x1141, + + /// + /// Indicates the symbol is a range for stack symbol. + /// + DefRangeFramePointerRel = 0x1142, + + /// + /// Indicates the symbol is a ranges for en-registered field of symbol + /// + DefRangeSubFieldRegister = 0x1143, + + /// + /// Indicates the symbol is a range for stack symbol span valid full scope of function body, gap might apply. + /// + DefRangeFramePointerRelFullScope = 0x1144, + + /// + /// Indicates the symbol is a range for symbol address as register + offset. + /// + DefRangeRegisterRel = 0x1145, + + // PROC symbols that reference ID instead of type + LProc32Id = 0x1146, + GProc32Id = 0x1147, + LProcMipId = 0x1148, + GProcMipId = 0x1149, + LProcIa64Id = 0x114a, + GProcIa64Id = 0x114b, + + /// + /// Indicates the symbol is a build information. + /// + BuildInfo = 0x114c, + + /// + /// Indicates the symbol is a inlined function callsite. + /// + InlineSite = 0x114d, + InlineSiteEnd = 0x114e, + ProcIdEnd = 0x114f, + + DefRangeHlsl = 0x1150, + GDataHlsl = 0x1151, + LDataHlsl = 0x1152, + + FileStatic = 0x1153, + + /// + /// Indicates the symbol is a DPC groupshared variable + /// + LocalDpcGroupShared = 0x1154, + + /// + /// Indicates the symbol is a DPC local procedure start + /// + LProc32Dpc = 0x1155, + LProc32DpcId = 0x1156, + + /// + /// Indicates the symbol is a DPC pointer tag definition range + /// + DefRangeDpcPtrTag = 0x1157, + + /// + /// Indicates the symbol is a DPC pointer tag value to symbol record map + /// + DpcSymTagMap = 0x1158, + + ArmSwitchTable = 0x1159, + Callees = 0x115a, + Callers = 0x115b, + PogoData = 0x115c, + + /// + /// Indicates the symbol is a extended inline site information + /// + InlineSite2 = 0x115d, + + /// + /// Indicates the symbol is a heap allocation site + /// + HeapAllocSite = 0x115e, + + /// + /// Indicates the symbol is a only generated at link time + /// + ModTypeRef = 0x115f, + + /// + /// Indicates the symbol is a only generated at link time for mini PDB + /// + RefMiniPdb = 0x1160, + + /// + /// Indicates the symbol is a only generated at link time for mini PDB + /// + PdbMap = 0x1161, + + GDataHlsl32 = 0x1162, + LDataHlsl32 = 0x1163, + + GDataHlsl32Ex = 0x1164, + LDataHlsl32Ex = 0x1165, + + RecTypeMax, // one greater than last + RecTypeLast = RecTypeMax - 1, + RecTypePad = RecTypeMax + 0x100 // Used *only* to verify symbol record types so that current PDB code can potentially read + // future PDBs (assuming no format change, etc). +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs new file mode 100644 index 000000000..650da299e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs @@ -0,0 +1,35 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol record for which the format is unknown or unsupported. +/// +public class UnknownSymbol : SymbolRecord +{ + /// + /// Creates a new unknown symbol record. + /// + /// The type of symbol. + /// The raw data stored in the record. + public UnknownSymbol(SymbolType symbolType, byte[] data) + { + SymbolType = symbolType; + Data = data; + } + + /// + public override SymbolType SymbolType + { + get; + } + + /// + /// Gets the raw data stored in the record. + /// + public byte[] Data + { + get; + } + + /// + public override string ToString() => $"{SymbolType.ToString()} ({Data.Length.ToString()} bytes)"; +} diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs new file mode 100644 index 000000000..8d4b6cacf --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; +using AsmResolver.Symbols.Pdb.Metadata.Info; +using AsmResolver.Symbols.Pdb.Msf; +using AsmResolver.Symbols.Pdb.Records; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides an implementation for a PDB image that is read from an input MSF file. +/// +public class SerializedPdbImage : PdbImage +{ + private readonly MsfFile _file; + + /// + /// Interprets a PDB image from the provided MSF file. + /// + /// The MSF file to read from. + public SerializedPdbImage(MsfFile file) + { + _file = file; + + InfoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); + DbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + } + + internal InfoStream InfoStream + { + get; + } + + internal DbiStream DbiStream + { + get; + } + + /// + protected override IList GetSymbols() + { + var result = new List(); + + int index = DbiStream.SymbolRecordStreamIndex; + if (index >= _file.Streams.Count) + return result; + + var reader = _file.Streams[DbiStream.SymbolRecordStreamIndex].CreateReader(); + while (reader.CanRead(sizeof(ushort) * 2)) + { + result.Add(SymbolRecord.FromReader(ref reader)); + } + + return result; + } +} From 5f9528c93265319ba0751736a222acc0d9c62cd4 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 15 Jul 2022 22:08:05 +0200 Subject: [PATCH 044/182] Add S_PUB32 symbol read support. --- .../Records/PublicSymbol.cs | 117 ++++++++++++++++++ .../Records/PublicSymbolAttributes.cs | 35 ++++++ .../Records/SymbolRecord.cs | 1 + .../Records/SymbolType.cs | 2 +- 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs new file mode 100644 index 000000000..cd51b365b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs @@ -0,0 +1,117 @@ +using System.Text; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a public symbol stored in a PDB symbol stream. +/// +public class PublicSymbol : SymbolRecord +{ + /// + /// Creates a new public symbol. + /// + /// The segment index. + /// The offset within the segment the symbol starts at. + /// The name of the symbol. + /// The attributes associated to the symbol. + public PublicSymbol(ushort segment, uint offset, string name, PublicSymbolAttributes attributes) + { + Segment = segment; + Offset = offset; + Name = name; + Attributes = attributes; + } + + /// + public override SymbolType SymbolType => SymbolType.Pub32; + + /// + /// Gets or sets the file segment index this symbol is located in. + /// + public ushort Segment + { + get; + set; + } + + /// + /// Gets or sets the offset within the file that this symbol is defined at. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets attributes associated to the public symbol. + /// + public PublicSymbolAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether the symbol is a code symbol. + /// + public bool IsCode + { + get => (Attributes & PublicSymbolAttributes.Code) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Code) + | (value ? PublicSymbolAttributes.Code : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol is a function symbol. + /// + public bool IsFunction + { + get => (Attributes & PublicSymbolAttributes.Function) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Function) + | (value ? PublicSymbolAttributes.Function : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol involves managed code. + /// + public bool IsManaged + { + get => (Attributes & PublicSymbolAttributes.Managed) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Managed) + | (value ? PublicSymbolAttributes.Managed : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol involves MSIL code. + /// + public bool IsMsil + { + get => (Attributes & PublicSymbolAttributes.Msil) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Msil) + | (value ? PublicSymbolAttributes.Msil : 0); + } + + /// + /// Gets or sets the name of the symbol. + /// + public string Name + { + get; + set; + } + + internal new static PublicSymbol FromReader(ref BinaryStreamReader reader) + { + var attributes = (PublicSymbolAttributes) reader.ReadUInt32(); + uint offset = reader.ReadUInt32(); + ushort segment = reader.ReadUInt16(); + string name = Encoding.ASCII.GetString(reader.ReadToEnd()); + + return new PublicSymbol(segment, offset, name, attributes); + } + + /// + public override string ToString() => $"{SymbolType}: [{Segment:X4}:{Offset:X8}] {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs new file mode 100644 index 000000000..a5e5824bd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs @@ -0,0 +1,35 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members defining all flags that can be associated to a public symbol. +/// +[Flags] +public enum PublicSymbolAttributes : uint +{ + /// + /// Indicates no flags are assigned to the symbol. + /// + None = 0, + + /// + /// Indicates the symbol is a code symbol. + /// + Code = 0x00000001, + + /// + /// Indicates the symbol is a function. + /// + Function = 0x00000002, + + /// + /// Indicates the symbol involves managed code. + /// + Managed = 0x00000004, + + /// + /// Indicates the symbol involves MSIL code. + /// + Msil = 0x00000008, +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs b/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs index f790e0c4c..367194152 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs @@ -29,6 +29,7 @@ public static SymbolRecord FromReader(ref BinaryStreamReader reader) return type switch { + SymbolType.Pub32 => PublicSymbol.FromReader(ref dataReader), _ => new UnknownSymbol(type, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs b/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs index dc72b2b72..617a15f02 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs @@ -1,6 +1,6 @@ namespace AsmResolver.Symbols.Pdb.Records; -// Reference: cvinfo.h +// Reference: microsoft-pdb/include/cvinfo.h /// /// Provides members defining all symbol record types that can be stored in a PDB symbol stream. From ee231d5d79ebea5c1a28c844bdd96cdff5b0e592 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 15 Jul 2022 22:18:04 +0200 Subject: [PATCH 045/182] Add S_UDT read support. --- .../Records/PublicSymbol.cs | 7 ++- .../Records/SymbolRecord.cs | 1 + .../Records/UserDefinedTypeSymbol.cs | 52 +++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs index cd51b365b..03dc89bee 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs @@ -1,4 +1,3 @@ -using System.Text; using AsmResolver.IO; namespace AsmResolver.Symbols.Pdb.Records; @@ -15,7 +14,7 @@ public class PublicSymbol : SymbolRecord /// The offset within the segment the symbol starts at. /// The name of the symbol. /// The attributes associated to the symbol. - public PublicSymbol(ushort segment, uint offset, string name, PublicSymbolAttributes attributes) + public PublicSymbol(ushort segment, uint offset, Utf8String name, PublicSymbolAttributes attributes) { Segment = segment; Offset = offset; @@ -96,7 +95,7 @@ public bool IsMsil /// /// Gets or sets the name of the symbol. /// - public string Name + public Utf8String Name { get; set; @@ -107,7 +106,7 @@ internal new static PublicSymbol FromReader(ref BinaryStreamReader reader) var attributes = (PublicSymbolAttributes) reader.ReadUInt32(); uint offset = reader.ReadUInt32(); ushort segment = reader.ReadUInt16(); - string name = Encoding.ASCII.GetString(reader.ReadToEnd()); + var name = new Utf8String(reader.ReadToEnd()); return new PublicSymbol(segment, offset, name, attributes); } diff --git a/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs b/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs index 367194152..bef026d8f 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs @@ -30,6 +30,7 @@ public static SymbolRecord FromReader(ref BinaryStreamReader reader) return type switch { SymbolType.Pub32 => PublicSymbol.FromReader(ref dataReader), + SymbolType.Udt => UserDefinedTypeSymbol.FromReader(ref dataReader), _ => new UnknownSymbol(type, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs new file mode 100644 index 000000000..d97bf5aae --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -0,0 +1,52 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a user-defined type symbol in a PDB symbol stream. +/// +public class UserDefinedTypeSymbol : SymbolRecord +{ + /// + /// Defines a new user-defined type. + /// + /// The name of the type. + /// The type index. + public UserDefinedTypeSymbol(Utf8String name, uint typeIndex) + { + Name = name; + TypeIndex = typeIndex; + } + + /// + public override SymbolType SymbolType => SymbolType.Udt; + + /// + /// Gets or sets the name of the type. + /// + public Utf8String Name + { + get; + set; + } + + /// + /// Gets or sets the index associated to the type. + /// + public uint TypeIndex + { + get; + set; + } + + internal new static UserDefinedTypeSymbol FromReader(ref BinaryStreamReader reader) + { + uint typeIndex = reader.ReadUInt32(); + var name = new Utf8String(reader.ReadToEnd()); + + return new UserDefinedTypeSymbol(name, typeIndex); + } + + /// + public override string ToString() => $"{SymbolType}: [{TypeIndex}] {Name}"; +} From 8e26e83f0adb67a937598e94a82ce2ee40fca709 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 15 Jul 2022 22:35:04 +0200 Subject: [PATCH 046/182] Add PDB stream index checks. --- src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index 8d4b6cacf..2c0703016 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using AsmResolver.Symbols.Pdb.Metadata.Dbi; using AsmResolver.Symbols.Pdb.Metadata.Info; @@ -21,6 +22,11 @@ public SerializedPdbImage(MsfFile file) { _file = file; + if (InfoStream.StreamIndex >= file.Streams.Count) + throw new BadImageFormatException("MSF file does not contain a PDB Info stream."); + if (InfoStream.StreamIndex >= file.Streams.Count) + throw new BadImageFormatException("MSF file does not contain a PDB Info stream."); + InfoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); DbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); } From f0cd900afdde0c8530c3e9173e859c37b4167195 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 16 Jul 2022 12:55:26 +0200 Subject: [PATCH 047/182] Rename SymbolRecord -> CodeViewSymbol. --- src/AsmResolver.Symbols.Pdb/PdbImage.cs | 6 +++--- .../Records/{SymbolRecord.cs => CodeViewSymbol.cs} | 12 ++++++------ .../Records/{SymbolType.cs => CodeViewSymbolType.cs} | 2 +- src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs | 6 +++--- src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs | 12 ++++++------ .../Records/UserDefinedTypeSymbol.cs | 6 +++--- src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs | 6 +++--- 7 files changed, 25 insertions(+), 25 deletions(-) rename src/AsmResolver.Symbols.Pdb/Records/{SymbolRecord.cs => CodeViewSymbol.cs} (66%) rename src/AsmResolver.Symbols.Pdb/Records/{SymbolType.cs => CodeViewSymbolType.cs} (99%) diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index 59fe4f361..199a9edab 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -11,12 +11,12 @@ namespace AsmResolver.Symbols.Pdb; /// public class PdbImage { - private IList? _symbols; + private IList? _symbols; /// /// Gets a collection of all symbols stored in the PDB image. /// - public IList Symbols + public IList Symbols { get { @@ -68,5 +68,5 @@ public IList Symbols /// /// This method is called upon initialization of the property. /// - protected virtual IList GetSymbols() => new List(); + protected virtual IList GetSymbols() => new List(); } diff --git a/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs similarity index 66% rename from src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs rename to src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs index bef026d8f..02714bef0 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/SymbolRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs @@ -5,12 +5,12 @@ namespace AsmResolver.Symbols.Pdb.Records; /// /// Represents a single symbol record within the symbol record stream of a PDB file. /// -public abstract class SymbolRecord +public abstract class CodeViewSymbol { /// /// Gets the type of symbol this record encodes. /// - public abstract SymbolType SymbolType + public abstract CodeViewSymbolType CodeViewSymbolType { get; } @@ -20,17 +20,17 @@ public abstract class SymbolRecord /// /// The input stream. /// The read symbol. - public static SymbolRecord FromReader(ref BinaryStreamReader reader) + public static CodeViewSymbol FromReader(ref BinaryStreamReader reader) { ushort length = reader.ReadUInt16(); - var type = (SymbolType) reader.ReadUInt16(); + var type = (CodeViewSymbolType) reader.ReadUInt16(); var dataReader = reader.ForkRelative(reader.RelativeOffset, (uint) (length - 2)); reader.Offset += (ulong) (length - 2); return type switch { - SymbolType.Pub32 => PublicSymbol.FromReader(ref dataReader), - SymbolType.Udt => UserDefinedTypeSymbol.FromReader(ref dataReader), + CodeViewSymbolType.Pub32 => PublicSymbol.FromReader(ref dataReader), + CodeViewSymbolType.Udt => UserDefinedTypeSymbol.FromReader(ref dataReader), _ => new UnknownSymbol(type, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs similarity index 99% rename from src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs rename to src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs index 617a15f02..11bb6e3ad 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/SymbolType.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Records; /// /// Provides members defining all symbol record types that can be stored in a PDB symbol stream. /// -public enum SymbolType : ushort +public enum CodeViewSymbolType : ushort { #pragma warning disable CS1591 /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs index 03dc89bee..814bea47a 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Records; /// /// Represents a public symbol stored in a PDB symbol stream. /// -public class PublicSymbol : SymbolRecord +public class PublicSymbol : CodeViewSymbol { /// /// Creates a new public symbol. @@ -23,7 +23,7 @@ public PublicSymbol(ushort segment, uint offset, Utf8String name, PublicSymbolAt } /// - public override SymbolType SymbolType => SymbolType.Pub32; + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Pub32; /// /// Gets or sets the file segment index this symbol is located in. @@ -112,5 +112,5 @@ internal new static PublicSymbol FromReader(ref BinaryStreamReader reader) } /// - public override string ToString() => $"{SymbolType}: [{Segment:X4}:{Offset:X8}] {Name}"; + public override string ToString() => $"{CodeViewSymbolType}: [{Segment:X4}:{Offset:X8}] {Name}"; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs index 650da299e..983173876 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs @@ -3,21 +3,21 @@ namespace AsmResolver.Symbols.Pdb.Records; /// /// Represents a symbol record for which the format is unknown or unsupported. /// -public class UnknownSymbol : SymbolRecord +public class UnknownSymbol : CodeViewSymbol { /// /// Creates a new unknown symbol record. /// - /// The type of symbol. + /// The type of symbol. /// The raw data stored in the record. - public UnknownSymbol(SymbolType symbolType, byte[] data) + public UnknownSymbol(CodeViewSymbolType codeViewSymbolType, byte[] data) { - SymbolType = symbolType; + CodeViewSymbolType = codeViewSymbolType; Data = data; } /// - public override SymbolType SymbolType + public override CodeViewSymbolType CodeViewSymbolType { get; } @@ -31,5 +31,5 @@ public byte[] Data } /// - public override string ToString() => $"{SymbolType.ToString()} ({Data.Length.ToString()} bytes)"; + public override string ToString() => $"{CodeViewSymbolType.ToString()} ({Data.Length.ToString()} bytes)"; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs index d97bf5aae..dcec87da4 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Records; /// /// Represents a user-defined type symbol in a PDB symbol stream. /// -public class UserDefinedTypeSymbol : SymbolRecord +public class UserDefinedTypeSymbol : CodeViewSymbol { /// /// Defines a new user-defined type. @@ -19,7 +19,7 @@ public UserDefinedTypeSymbol(Utf8String name, uint typeIndex) } /// - public override SymbolType SymbolType => SymbolType.Udt; + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Udt; /// /// Gets or sets the name of the type. @@ -48,5 +48,5 @@ internal new static UserDefinedTypeSymbol FromReader(ref BinaryStreamReader read } /// - public override string ToString() => $"{SymbolType}: [{TypeIndex}] {Name}"; + public override string ToString() => $"{CodeViewSymbolType}: [{TypeIndex}] {Name}"; } diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index 2c0703016..4ed5dacf6 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -42,9 +42,9 @@ internal DbiStream DbiStream } /// - protected override IList GetSymbols() + protected override IList GetSymbols() { - var result = new List(); + var result = new List(); int index = DbiStream.SymbolRecordStreamIndex; if (index >= _file.Streams.Count) @@ -53,7 +53,7 @@ protected override IList GetSymbols() var reader = _file.Streams[DbiStream.SymbolRecordStreamIndex].CreateReader(); while (reader.CanRead(sizeof(ushort) * 2)) { - result.Add(SymbolRecord.FromReader(ref reader)); + result.Add(CodeViewSymbol.FromReader(ref reader)); } return result; From 51701150f9ac61c4eedd60cf7976cb7511f2799e Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 16 Jul 2022 18:26:00 +0200 Subject: [PATCH 048/182] Add basic TPI/IPI read support. --- .../AsmResolver.Symbols.Pdb.csproj | 7 + .../Metadata/Tpi/SerializedTpiStream.cs | 87 +++++++ .../Metadata/Tpi/TpiStream.cs | 154 +++++++++++++ .../Metadata/Tpi/TpiStreamVersion.cs | 10 + src/AsmResolver.Symbols.Pdb/PdbImage.cs | 28 +++ .../SerializedPdbImage.cs | 54 ++++- .../Types/CodeViewType.cs | 37 +++ .../Types/CodeViewTypeKind.cs | 216 ++++++++++++++++++ .../Types/UnknownCodeViewType.cs | 35 +++ 9 files changed, 621 insertions(+), 7 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj index 168f407cc..091cf3c05 100644 --- a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj +++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj @@ -25,4 +25,11 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs new file mode 100644 index 000000000..4f11efe90 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +public class SerializedTpiStream : TpiStream +{ + private readonly BinaryStreamReader _recordsReader; + private List<(uint Offset, uint Length)>? _recordOffsets; + + public SerializedTpiStream(BinaryStreamReader reader) + { + Version = (TpiStreamVersion) reader.ReadUInt32(); + if (Version != TpiStreamVersion.V80) + throw new NotSupportedException($"Unsupported TPI file format version {Version}."); + + uint headerSize = reader.ReadUInt32(); + if (headerSize != TpiStreamHeaderSize) + throw new NotSupportedException("Invalid TPI header size."); + + TypeIndexBegin = reader.ReadUInt32(); + TypeIndexEnd = reader.ReadUInt32(); + TypeRecordsByteCount = reader.ReadUInt32(); + HashStreamIndex = reader.ReadUInt16(); + HashAuxStreamIndex = reader.ReadUInt16(); + HashKeySize = reader.ReadUInt32(); + HashBucketCount = reader.ReadUInt32(); + HashValueBufferOffset = reader.ReadUInt32(); + HashValueBufferLength = reader.ReadUInt32(); + IndexOffsetBufferOffset = reader.ReadUInt32(); + IndexOffsetBufferLength = reader.ReadUInt32(); + HashAdjBufferOffset = reader.ReadUInt32(); + HashAdjBufferLength = reader.ReadUInt32(); + + _recordsReader = reader.ForkRelative(reader.RelativeOffset, TypeRecordsByteCount); + } + + [MemberNotNull(nameof(_recordOffsets))] + private void EnsureRecordOffsetMappingInitialized() + { + if (_recordOffsets is null) + Interlocked.CompareExchange(ref _recordOffsets, GetRecordOffsets(), null); + } + + private List<(uint Offset, uint Length)> GetRecordOffsets() + { + int count = (int) (TypeIndexEnd - TypeIndexBegin); + var result = new List<(uint Offset, uint Length)>(count); + + var reader = _recordsReader.Fork(); + while (reader.CanRead(sizeof(ushort) * 2)) + { + uint offset = reader.RelativeOffset; + ushort length = reader.ReadUInt16(); + result.Add((offset, length)); + reader.Offset += length; + } + + return result; + } + + /// + public override bool TryGetTypeRecordReader(uint typeIndex, out BinaryStreamReader reader) + { + EnsureRecordOffsetMappingInitialized(); + + typeIndex -= TypeIndexBegin; + if (typeIndex >= _recordOffsets.Count) + { + reader = default; + return false; + } + + (uint offset, uint length) = _recordOffsets[(int) typeIndex]; + reader = _recordsReader.ForkRelative(offset, length); + return true; + } + + /// + protected override void WriteTypeRecords(IBinaryStreamWriter writer) + { + _recordsReader.Fork().WriteToOutput(writer); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs new file mode 100644 index 000000000..e9fe42889 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs @@ -0,0 +1,154 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +public abstract class TpiStream : SegmentBase +{ + public const int StreamIndex = 2; + + internal const uint TpiStreamHeaderSize = + sizeof(TpiStreamVersion) // Version + + sizeof(uint) // HeaderSize + + sizeof(uint) // TypeIndexBegin + + sizeof(uint) // TypeIndexEnd + + sizeof(uint) // TypeRecordBytes + + sizeof(ushort) // HashStreamIndex + + sizeof(ushort) // HashAuxStreamIndex + + sizeof(uint) // HashKeySize + + sizeof(uint) // NumHashBuckets + + sizeof(uint) // HashValueBufferOffset + + sizeof(uint) // HashValueBufferLength + + sizeof(uint) // IndexOffsetBufferOffset + + sizeof(uint) // IndexOffsetBufferLength + + sizeof(uint) // HashAdjBufferOffset + + sizeof(uint) // HashAdjBufferLength + ; + + public TpiStreamVersion Version + { + get; + set; + } = TpiStreamVersion.V80; + + public uint TypeIndexBegin + { + get; + set; + } = 0x1000; + + public uint TypeIndexEnd + { + get; + set; + } + + public uint TypeRecordsByteCount + { + get; + set; + } + + public ushort HashStreamIndex + { + get; + set; + } + + public ushort HashAuxStreamIndex + { + get; + set; + } + + public uint HashKeySize + { + get; + set; + } + + public uint HashBucketCount + { + get; + set; + } + + public uint HashValueBufferOffset + { + get; + set; + } + + public uint HashValueBufferLength + { + get; + set; + } + + public uint IndexOffsetBufferOffset + { + get; + set; + } + + public uint IndexOffsetBufferLength + { + get; + set; + } + + public uint HashAdjBufferOffset + { + get; + set; + } + + public uint HashAdjBufferLength + { + get; + set; + } + + public static TpiStream FromReader(BinaryStreamReader reader) => new SerializedTpiStream(reader); + + public abstract bool TryGetTypeRecordReader(uint typeIndex, out BinaryStreamReader reader); + + public BinaryStreamReader GetTypeRecordReader(uint typeIndex) + { + if (!TryGetTypeRecordReader(typeIndex, out var reader)) + throw new ArgumentException("Invalid type index."); + + return reader; + } + + /// + public override uint GetPhysicalSize() => TpiStreamHeaderSize + TypeRecordsByteCount; + + /// + public override void Write(IBinaryStreamWriter writer) + { + WriteHeader(writer); + WriteTypeRecords(writer); + } + + private void WriteHeader(IBinaryStreamWriter writer) + { + writer.WriteUInt32((uint) Version); + writer.WriteUInt32(TpiStreamHeaderSize); + writer.WriteUInt32(TypeIndexBegin); + writer.WriteUInt32(TypeIndexEnd); + writer.WriteUInt32(TypeRecordsByteCount); + writer.WriteUInt16(HashStreamIndex); + writer.WriteUInt16(HashAuxStreamIndex); + writer.WriteUInt32(HashKeySize); + writer.WriteUInt32(HashBucketCount); + writer.WriteUInt32(HashValueBufferOffset); + writer.WriteUInt32(HashValueBufferLength); + writer.WriteUInt32(IndexOffsetBufferOffset); + writer.WriteUInt32(IndexOffsetBufferLength); + writer.WriteUInt32(HashAdjBufferOffset); + writer.WriteUInt32(HashAdjBufferLength); + } + + protected abstract void WriteTypeRecords(IBinaryStreamWriter writer); +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs new file mode 100644 index 000000000..dd20f4312 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs @@ -0,0 +1,10 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +public enum TpiStreamVersion : uint +{ + V40 = 19950410, + V41 = 19951122, + V50 = 19961031, + V70 = 19990903, + V80 = 20040203, +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index 199a9edab..11443e0e3 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -1,8 +1,11 @@ +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using AsmResolver.IO; using AsmResolver.Symbols.Pdb.Msf; using AsmResolver.Symbols.Pdb.Records; +using AsmResolver.Symbols.Pdb.Types; namespace AsmResolver.Symbols.Pdb; @@ -61,6 +64,31 @@ public IList Symbols /// The read PDB image. public static PdbImage FromFile(MsfFile file) => new SerializedPdbImage(file); + /// + /// Attempts to obtain a type record from the TPI or IPI stream based on its type index. + /// + /// The type index. + /// The resolved type. + /// true if the type was found, false otherwise. + public virtual bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewType? type) + { + type = null; + return false; + } + + /// + /// Obtains a type record from the TPI or IPI stream based on its type index. + /// + /// The type index. + /// The resolved type. + /// Occurs when the type index is invalid. + public CodeViewType GetTypeRecord(uint typeIndex) + { + if (!TryGetTypeRecord(typeIndex, out var type)) + throw new ArgumentException("Invalid type index."); + return type; + } + /// /// Obtains a collection of symbols stored in the symbol record stream of the PDB image. /// diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index 4ed5dacf6..3e7e2c206 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; using AsmResolver.Symbols.Pdb.Metadata.Dbi; using AsmResolver.Symbols.Pdb.Metadata.Info; +using AsmResolver.Symbols.Pdb.Metadata.Tpi; using AsmResolver.Symbols.Pdb.Msf; using AsmResolver.Symbols.Pdb.Records; +using AsmResolver.Symbols.Pdb.Types; namespace AsmResolver.Symbols.Pdb; @@ -12,7 +16,9 @@ namespace AsmResolver.Symbols.Pdb; /// public class SerializedPdbImage : PdbImage { + private const int MinimalRequiredStreamCount = 5; private readonly MsfFile _file; + private CodeViewType?[]? _types; /// /// Interprets a PDB image from the provided MSF file. @@ -22,14 +28,13 @@ public SerializedPdbImage(MsfFile file) { _file = file; - if (InfoStream.StreamIndex >= file.Streams.Count) - throw new BadImageFormatException("MSF file does not contain a PDB Info stream."); - if (InfoStream.StreamIndex >= file.Streams.Count) - throw new BadImageFormatException("MSF file does not contain a PDB Info stream."); + if (file.Streams.Count < MinimalRequiredStreamCount) + throw new BadImageFormatException("MSF does not contain the minimal required amount of streams."); InfoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); DbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); - } + TpiStream = TpiStream.FromReader(file.Streams[TpiStream.StreamIndex].CreateReader()); + } internal InfoStream InfoStream { @@ -41,6 +46,43 @@ internal DbiStream DbiStream get; } + internal TpiStream TpiStream + { + get; + } + + [MemberNotNull(nameof(_types))] + private void EnsureTypeArrayInitialized() + { + if (_types is null) + { + Interlocked.CompareExchange(ref _types, + new CodeViewType?[TpiStream.TypeIndexEnd - TpiStream.TypeIndexBegin], null); + } + } + + public override bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewType? type) + { + EnsureTypeArrayInitialized(); + + if (typeIndex >= TpiStream.TypeIndexBegin && typeIndex < TpiStream.TypeIndexEnd) + { + type = _types[typeIndex - TpiStream.TypeIndexBegin]; + if (type is null && TpiStream.TryGetTypeRecordReader(typeIndex, out var reader)) + { + type = CodeViewType.FromReader(this, reader); + type.TypeIndex = typeIndex; + Interlocked.CompareExchange(ref _types[typeIndex - TpiStream.TypeIndexBegin], type, null); + } + + type = _types[typeIndex - TpiStream.TypeIndexBegin]; + return type is not null; + } + + type = null; + return false; + } + /// protected override IList GetSymbols() { @@ -52,9 +94,7 @@ protected override IList GetSymbols() var reader = _file.Streams[DbiStream.SymbolRecordStreamIndex].CreateReader(); while (reader.CanRead(sizeof(ushort) * 2)) - { result.Add(CodeViewSymbol.FromReader(ref reader)); - } return result; } diff --git a/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs new file mode 100644 index 000000000..364b9d502 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs @@ -0,0 +1,37 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Types; + +/// +/// Represents a single type record in a TPI or IPI stream. +/// +public abstract class CodeViewType +{ + /// + /// Gets the type kind this record encodes. + /// + public abstract CodeViewTypeKind TypeKind + { + get; + } + + /// + /// Gets the type index the type is associated to. + /// + public uint TypeIndex + { + get; + internal set; + } + + internal static CodeViewType FromReader(PdbImage owner, BinaryStreamReader reader) + { + ushort length = reader.ReadUInt16(); + var kind = (CodeViewTypeKind) reader.ReadUInt16(); + + return kind switch + { + _ => new UnknownCodeViewType(kind, reader.ReadToEnd()) + }; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs b/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs new file mode 100644 index 000000000..81babe5b8 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs @@ -0,0 +1,216 @@ +namespace AsmResolver.Symbols.Pdb.Types; + +/// +/// Provides members defining all possible type record kinds that can be stored in a TPI or IPI stream. +/// +public enum CodeViewTypeKind : ushort +{ +#pragma warning disable CS1591 + Modifier16T = 0x0001, + Pointer16T = 0x0002, + Array16T = 0x0003, + Class16T = 0x0004, + Structure16T = 0x0005, + Union16T = 0x0006, + Enum16T = 0x0007, + Procedure16T = 0x0008, + MFunction16T = 0x0009, + VTShape = 0x000a, + Cobol016T = 0x000b, + Cobol1 = 0x000c, + Barray16T = 0x000d, + Label = 0x000e, + Null = 0x000f, + Nottran = 0x0010, + DimArray16T = 0x0011, + VftPath16T = 0x0012, + PreComp16T = 0x0013, // not referenced from symbol + EndPreComp = 0x0014, // not referenced from symbol + Oem16T = 0x0015, // oem definable type string + TypeServerSt = 0x0016, // not referenced from symbol + + // leaf indices starting records but referenced only from type records + + Skip16T = 0x0200, + ArgList16T = 0x0201, + DefArg16T = 0x0202, + List = 0x0203, + FieldList16T = 0x0204, + Derived16T = 0x0205, + BitField16T = 0x0206, + MethodList16T = 0x0207, + DimConU16T = 0x0208, + DimConLu16T = 0x0209, + DimVarU16T = 0x020a, + DimVarLu16T = 0x020b, + RefSym = 0x020c, + + BClass16T = 0x0400, + VBClass16T = 0x0401, + IVBClass16T = 0x0402, + EnumerateSt = 0x0403, + FriendFcn16T = 0x0404, + Index16T = 0x0405, + Member16T = 0x0406, + StMember16T = 0x0407, + Method16T = 0x0408, + NestType16T = 0x0409, + VFuncTab16T = 0x040a, + FriendCls16T = 0x040b, + OneMethod16T = 0x040c, + VFuncOff16T = 0x040d, + +// 32-bit type index versions of leaves, all have the 0x1000 bit set +// + Ti16Max = 0x1000, + + Modifier = 0x1001, + Pointer = 0x1002, + ArraySt = 0x1003, + ClassSt = 0x1004, + StructureSt = 0x1005, + UnionSt = 0x1006, + EnumSt = 0x1007, + Procedure = 0x1008, + MFunction = 0x1009, + Cobol0 = 0x100a, + BArray = 0x100b, + DimArraySt = 0x100c, + VftPath = 0x100d, + PreCompSt = 0x100e, // not referenced from symbol + Oem = 0x100f, // oem definable type string + AliasSt = 0x1010, // alias (typedef) type + Oem2 = 0x1011, // oem definable type string + + // leaf indices starting records but referenced only from type records + + Skip = 0x1200, + Arglist = 0x1201, + DefArgSt = 0x1202, + FieldList = 0x1203, + Derived = 0x1204, + BitField = 0x1205, + MethodList = 0x1206, + DimConU = 0x1207, + DimConLu = 0x1208, + DimVarU = 0x1209, + DimVarLu = 0x120a, + + BClass = 0x1400, + VBClass = 0x1401, + IVBClass = 0x1402, + FriendFcnSt = 0x1403, + Index = 0x1404, + MemberSt = 0x1405, + StmemberSt = 0x1406, + MethodSt = 0x1407, + NestTypeSt = 0x1408, + VFuncTab = 0x1409, + FriendCls = 0x140a, + OneMethodSt = 0x140b, + VFuncOff = 0x140c, + NestTypeExSt = 0x140d, + MemberModifySt = 0x140e, + ManagedSt = 0x140f, + + // Types w/ SZ names + + StMax = 0x1500, + + TypeServer = 0x1501, // not referenced from symbol + Enumerate = 0x1502, + Array = 0x1503, + Class = 0x1504, + Structure = 0x1505, + Union = 0x1506, + Enum = 0x1507, + DimArray = 0x1508, + PreComp = 0x1509, // not referenced from symbol + Alias = 0x150a, // alias (typedef) type + DefArg = 0x150b, + FriendFcn = 0x150c, + Member = 0x150d, + StMember = 0x150e, + Method = 0x150f, + NestType = 0x1510, + OneMethod = 0x1511, + NestTypeEx = 0x1512, + MemberModify = 0x1513, + Managed = 0x1514, + TypeServer2 = 0x1515, + + StridedArray = 0x1516, // same as ARRAY, but with stride between adjacent elements + Hlsl = 0x1517, + ModifierEx = 0x1518, + Interface = 0x1519, + BInterface = 0x151a, + Vector = 0x151b, + Matrix = 0x151c, + + VFTable = 0x151d, // a virtual function table + EndOfLeafRecord = VFTable, + + TypeLast, // one greater than the last type record + TypeMax = TypeLast - 1, + + FuncId = 0x1601, // global func ID + MFuncId = 0x1602, // member func ID + Buildinfo = 0x1603, // build info: tool, version, command line, src/pdb file + SubstrList = 0x1604, // similar to ARGLIST, for list of sub strings + StringId = 0x1605, // string ID + + UdtSrcLine = 0x1606, // source and line on where an UDT is defined + // only generated by compiler + + UdtModSrcLine = 0x1607, // module, source and line on where an UDT is defined + // only generated by linker + + IdLast, // one greater than the last ID record + IdMax = IdLast - 1, + + Numeric = 0x8000, + Char = 0x8000, + Short = 0x8001, + UShort = 0x8002, + Long = 0x8003, + ULong = 0x8004, + Real32 = 0x8005, + Real64 = 0x8006, + Real80 = 0x8007, + Real128 = 0x8008, + QuadWord = 0x8009, + UQuadWord = 0x800a, + Real48 = 0x800b, + Complex32 = 0x800c, + Complex64 = 0x800d, + Complex80 = 0x800e, + Complex128 = 0x800f, + VarString = 0x8010, + + OctWord = 0x8017, + UOctWord = 0x8018, + + Decimal = 0x8019, + Date = 0x801a, + Utf8String = 0x801b, + + Real16 = 0x801c, + + Pad0 = 0xf0, + Pad1 = 0xf1, + Pad2 = 0xf2, + Pad3 = 0xf3, + Pad4 = 0xf4, + Pad5 = 0xf5, + Pad6 = 0xf6, + Pad7 = 0xf7, + Pad8 = 0xf8, + Pad9 = 0xf9, + Pad10 = 0xfa, + Pad11 = 0xfb, + Pad12 = 0xfc, + Pad13 = 0xfd, + Pad14 = 0xfe, + Pad15 = 0xff, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs new file mode 100644 index 000000000..3f0215798 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs @@ -0,0 +1,35 @@ +namespace AsmResolver.Symbols.Pdb.Types; + +/// +/// Represents an unknown or unsupported CodeView type record. +/// +public class UnknownCodeViewType : CodeViewType +{ + /// + /// Creates a new unknown type record. + /// + /// The type of symbol. + /// The raw data stored in the record. + public UnknownCodeViewType(CodeViewTypeKind typeKind, byte[] data) + { + TypeKind = typeKind; + Data = data; + } + + /// + public override CodeViewTypeKind TypeKind + { + get; + } + + /// + /// Gets the raw data stored in the record. + /// + public byte[] Data + { + get; + } + + /// + public override string ToString() => $"{TypeKind.ToString()} ({Data.Length.ToString()} bytes)"; +} From 92760d73fdb4f9abc566e92e9445aeac1b9f4130 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 20 Jul 2022 22:41:28 +0200 Subject: [PATCH 049/182] Add simple type index parsing. --- src/AsmResolver.Symbols.Pdb/PdbImage.cs | 9 + .../SerializedPdbImage.cs | 5 + .../Types/CodeViewTypeKind.cs | 393 +++++++++--------- .../Types/SimpleType.cs | 56 +++ .../Types/SimpleTypeKind.cs | 250 +++++++++++ .../Types/SimpleTypeMode.cs | 50 +++ 6 files changed, 569 insertions(+), 194 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Types/SimpleTypeKind.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Types/SimpleTypeMode.cs diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index 11443e0e3..a36d93527 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -15,6 +16,7 @@ namespace AsmResolver.Symbols.Pdb; public class PdbImage { private IList? _symbols; + private ConcurrentDictionary _simpleTypes = new(); /// /// Gets a collection of all symbols stored in the PDB image. @@ -72,6 +74,13 @@ public IList Symbols /// true if the type was found, false otherwise. public virtual bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewType? type) { + typeIndex &= 0x7fffffff; + if (typeIndex < 0x1000) + { + type = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleType(i)); + return true; + } + type = null; return false; } diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index 3e7e2c206..0e49fb129 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -61,8 +62,12 @@ private void EnsureTypeArrayInitialized() } } + /// public override bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewType? type) { + if (typeIndex < TpiStream.TypeIndexBegin) + return base.TryGetTypeRecord(typeIndex, out type); + EnsureTypeArrayInitialized(); if (typeIndex >= TpiStream.TypeIndexBegin && typeIndex < TpiStream.TypeIndexEnd) diff --git a/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs b/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs index 81babe5b8..905649af4 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs +++ b/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs @@ -6,211 +6,216 @@ namespace AsmResolver.Symbols.Pdb.Types; public enum CodeViewTypeKind : ushort { #pragma warning disable CS1591 - Modifier16T = 0x0001, - Pointer16T = 0x0002, - Array16T = 0x0003, - Class16T = 0x0004, - Structure16T = 0x0005, - Union16T = 0x0006, - Enum16T = 0x0007, - Procedure16T = 0x0008, - MFunction16T = 0x0009, - VTShape = 0x000a, - Cobol016T = 0x000b, - Cobol1 = 0x000c, - Barray16T = 0x000d, - Label = 0x000e, - Null = 0x000f, - Nottran = 0x0010, - DimArray16T = 0x0011, - VftPath16T = 0x0012, - PreComp16T = 0x0013, // not referenced from symbol - EndPreComp = 0x0014, // not referenced from symbol - Oem16T = 0x0015, // oem definable type string - TypeServerSt = 0x0016, // not referenced from symbol + /// + /// This is not really a type defined in PDB spec, but is used to indicate simple types. + /// + SimpleType = 0xffff, + + Modifier16T = 0x0001, + Pointer16T = 0x0002, + Array16T = 0x0003, + Class16T = 0x0004, + Structure16T = 0x0005, + Union16T = 0x0006, + Enum16T = 0x0007, + Procedure16T = 0x0008, + MFunction16T = 0x0009, + VTShape = 0x000a, + Cobol016T = 0x000b, + Cobol1 = 0x000c, + Barray16T = 0x000d, + Label = 0x000e, + Null = 0x000f, + Nottran = 0x0010, + DimArray16T = 0x0011, + VftPath16T = 0x0012, + PreComp16T = 0x0013, // not referenced from symbol + EndPreComp = 0x0014, // not referenced from symbol + Oem16T = 0x0015, // oem definable type string + TypeServerSt = 0x0016, // not referenced from symbol // leaf indices starting records but referenced only from type records - Skip16T = 0x0200, - ArgList16T = 0x0201, - DefArg16T = 0x0202, - List = 0x0203, - FieldList16T = 0x0204, - Derived16T = 0x0205, - BitField16T = 0x0206, - MethodList16T = 0x0207, - DimConU16T = 0x0208, - DimConLu16T = 0x0209, - DimVarU16T = 0x020a, - DimVarLu16T = 0x020b, - RefSym = 0x020c, - - BClass16T = 0x0400, - VBClass16T = 0x0401, - IVBClass16T = 0x0402, - EnumerateSt = 0x0403, - FriendFcn16T = 0x0404, - Index16T = 0x0405, - Member16T = 0x0406, - StMember16T = 0x0407, - Method16T = 0x0408, - NestType16T = 0x0409, - VFuncTab16T = 0x040a, - FriendCls16T = 0x040b, - OneMethod16T = 0x040c, - VFuncOff16T = 0x040d, + Skip16T = 0x0200, + ArgList16T = 0x0201, + DefArg16T = 0x0202, + List = 0x0203, + FieldList16T = 0x0204, + Derived16T = 0x0205, + BitField16T = 0x0206, + MethodList16T = 0x0207, + DimConU16T = 0x0208, + DimConLu16T = 0x0209, + DimVarU16T = 0x020a, + DimVarLu16T = 0x020b, + RefSym = 0x020c, + + BClass16T = 0x0400, + VBClass16T = 0x0401, + IVBClass16T = 0x0402, + EnumerateSt = 0x0403, + FriendFcn16T = 0x0404, + Index16T = 0x0405, + Member16T = 0x0406, + StMember16T = 0x0407, + Method16T = 0x0408, + NestType16T = 0x0409, + VFuncTab16T = 0x040a, + FriendCls16T = 0x040b, + OneMethod16T = 0x040c, + VFuncOff16T = 0x040d, // 32-bit type index versions of leaves, all have the 0x1000 bit set // - Ti16Max = 0x1000, - - Modifier = 0x1001, - Pointer = 0x1002, - ArraySt = 0x1003, - ClassSt = 0x1004, - StructureSt = 0x1005, - UnionSt = 0x1006, - EnumSt = 0x1007, - Procedure = 0x1008, - MFunction = 0x1009, - Cobol0 = 0x100a, - BArray = 0x100b, - DimArraySt = 0x100c, - VftPath = 0x100d, - PreCompSt = 0x100e, // not referenced from symbol - Oem = 0x100f, // oem definable type string - AliasSt = 0x1010, // alias (typedef) type - Oem2 = 0x1011, // oem definable type string + Ti16Max = 0x1000, + + Modifier = 0x1001, + Pointer = 0x1002, + ArraySt = 0x1003, + ClassSt = 0x1004, + StructureSt = 0x1005, + UnionSt = 0x1006, + EnumSt = 0x1007, + Procedure = 0x1008, + MFunction = 0x1009, + Cobol0 = 0x100a, + BArray = 0x100b, + DimArraySt = 0x100c, + VftPath = 0x100d, + PreCompSt = 0x100e, // not referenced from symbol + Oem = 0x100f, // oem definable type string + AliasSt = 0x1010, // alias (typedef) type + Oem2 = 0x1011, // oem definable type string // leaf indices starting records but referenced only from type records - Skip = 0x1200, - Arglist = 0x1201, - DefArgSt = 0x1202, - FieldList = 0x1203, - Derived = 0x1204, - BitField = 0x1205, - MethodList = 0x1206, - DimConU = 0x1207, - DimConLu = 0x1208, - DimVarU = 0x1209, - DimVarLu = 0x120a, - - BClass = 0x1400, - VBClass = 0x1401, - IVBClass = 0x1402, - FriendFcnSt = 0x1403, - Index = 0x1404, - MemberSt = 0x1405, - StmemberSt = 0x1406, - MethodSt = 0x1407, - NestTypeSt = 0x1408, - VFuncTab = 0x1409, - FriendCls = 0x140a, - OneMethodSt = 0x140b, - VFuncOff = 0x140c, - NestTypeExSt = 0x140d, - MemberModifySt = 0x140e, - ManagedSt = 0x140f, + Skip = 0x1200, + Arglist = 0x1201, + DefArgSt = 0x1202, + FieldList = 0x1203, + Derived = 0x1204, + BitField = 0x1205, + MethodList = 0x1206, + DimConU = 0x1207, + DimConLu = 0x1208, + DimVarU = 0x1209, + DimVarLu = 0x120a, + + BClass = 0x1400, + VBClass = 0x1401, + IVBClass = 0x1402, + FriendFcnSt = 0x1403, + Index = 0x1404, + MemberSt = 0x1405, + StmemberSt = 0x1406, + MethodSt = 0x1407, + NestTypeSt = 0x1408, + VFuncTab = 0x1409, + FriendCls = 0x140a, + OneMethodSt = 0x140b, + VFuncOff = 0x140c, + NestTypeExSt = 0x140d, + MemberModifySt = 0x140e, + ManagedSt = 0x140f, // Types w/ SZ names - StMax = 0x1500, - - TypeServer = 0x1501, // not referenced from symbol - Enumerate = 0x1502, - Array = 0x1503, - Class = 0x1504, - Structure = 0x1505, - Union = 0x1506, - Enum = 0x1507, - DimArray = 0x1508, - PreComp = 0x1509, // not referenced from symbol - Alias = 0x150a, // alias (typedef) type - DefArg = 0x150b, - FriendFcn = 0x150c, - Member = 0x150d, - StMember = 0x150e, - Method = 0x150f, - NestType = 0x1510, - OneMethod = 0x1511, - NestTypeEx = 0x1512, - MemberModify = 0x1513, - Managed = 0x1514, - TypeServer2 = 0x1515, - - StridedArray = 0x1516, // same as ARRAY, but with stride between adjacent elements - Hlsl = 0x1517, - ModifierEx = 0x1518, - Interface = 0x1519, - BInterface = 0x151a, - Vector = 0x151b, - Matrix = 0x151c, - - VFTable = 0x151d, // a virtual function table - EndOfLeafRecord = VFTable, - - TypeLast, // one greater than the last type record - TypeMax = TypeLast - 1, - - FuncId = 0x1601, // global func ID - MFuncId = 0x1602, // member func ID - Buildinfo = 0x1603, // build info: tool, version, command line, src/pdb file - SubstrList = 0x1604, // similar to ARGLIST, for list of sub strings - StringId = 0x1605, // string ID - - UdtSrcLine = 0x1606, // source and line on where an UDT is defined - // only generated by compiler - - UdtModSrcLine = 0x1607, // module, source and line on where an UDT is defined - // only generated by linker - - IdLast, // one greater than the last ID record - IdMax = IdLast - 1, - - Numeric = 0x8000, - Char = 0x8000, - Short = 0x8001, - UShort = 0x8002, - Long = 0x8003, - ULong = 0x8004, - Real32 = 0x8005, - Real64 = 0x8006, - Real80 = 0x8007, - Real128 = 0x8008, - QuadWord = 0x8009, - UQuadWord = 0x800a, - Real48 = 0x800b, - Complex32 = 0x800c, - Complex64 = 0x800d, - Complex80 = 0x800e, - Complex128 = 0x800f, - VarString = 0x8010, - - OctWord = 0x8017, - UOctWord = 0x8018, - - Decimal = 0x8019, - Date = 0x801a, - Utf8String = 0x801b, - - Real16 = 0x801c, - - Pad0 = 0xf0, - Pad1 = 0xf1, - Pad2 = 0xf2, - Pad3 = 0xf3, - Pad4 = 0xf4, - Pad5 = 0xf5, - Pad6 = 0xf6, - Pad7 = 0xf7, - Pad8 = 0xf8, - Pad9 = 0xf9, - Pad10 = 0xfa, - Pad11 = 0xfb, - Pad12 = 0xfc, - Pad13 = 0xfd, - Pad14 = 0xfe, - Pad15 = 0xff, + StMax = 0x1500, + + TypeServer = 0x1501, // not referenced from symbol + Enumerate = 0x1502, + Array = 0x1503, + Class = 0x1504, + Structure = 0x1505, + Union = 0x1506, + Enum = 0x1507, + DimArray = 0x1508, + PreComp = 0x1509, // not referenced from symbol + Alias = 0x150a, // alias (typedef) type + DefArg = 0x150b, + FriendFcn = 0x150c, + Member = 0x150d, + StMember = 0x150e, + Method = 0x150f, + NestType = 0x1510, + OneMethod = 0x1511, + NestTypeEx = 0x1512, + MemberModify = 0x1513, + Managed = 0x1514, + TypeServer2 = 0x1515, + + StridedArray = 0x1516, // same as ARRAY, but with stride between adjacent elements + Hlsl = 0x1517, + ModifierEx = 0x1518, + Interface = 0x1519, + BInterface = 0x151a, + Vector = 0x151b, + Matrix = 0x151c, + + VFTable = 0x151d, // a virtual function table + EndOfLeafRecord = VFTable, + + TypeLast, // one greater than the last type record + TypeMax = TypeLast - 1, + + FuncId = 0x1601, // global func ID + MFuncId = 0x1602, // member func ID + Buildinfo = 0x1603, // build info: tool, version, command line, src/pdb file + SubstrList = 0x1604, // similar to ARGLIST, for list of sub strings + StringId = 0x1605, // string ID + + UdtSrcLine = 0x1606, // source and line on where an UDT is defined + // only generated by compiler + + UdtModSrcLine = 0x1607, // module, source and line on where an UDT is defined + // only generated by linker + + IdLast, // one greater than the last ID record + IdMax = IdLast - 1, + + Numeric = 0x8000, + Char = 0x8000, + Short = 0x8001, + UShort = 0x8002, + Long = 0x8003, + ULong = 0x8004, + Real32 = 0x8005, + Real64 = 0x8006, + Real80 = 0x8007, + Real128 = 0x8008, + QuadWord = 0x8009, + UQuadWord = 0x800a, + Real48 = 0x800b, + Complex32 = 0x800c, + Complex64 = 0x800d, + Complex80 = 0x800e, + Complex128 = 0x800f, + VarString = 0x8010, + + OctWord = 0x8017, + UOctWord = 0x8018, + + Decimal = 0x8019, + Date = 0x801a, + Utf8String = 0x801b, + + Real16 = 0x801c, + + Pad0 = 0xf0, + Pad1 = 0xf1, + Pad2 = 0xf2, + Pad3 = 0xf3, + Pad4 = 0xf4, + Pad5 = 0xf5, + Pad6 = 0xf6, + Pad7 = 0xf7, + Pad8 = 0xf8, + Pad9 = 0xf9, + Pad10 = 0xfa, + Pad11 = 0xfb, + Pad12 = 0xfc, + Pad13 = 0xfd, + Pad14 = 0xfe, + Pad15 = 0xff, #pragma warning restore CS1591 } diff --git a/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs b/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs new file mode 100644 index 000000000..7ffc09dda --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs @@ -0,0 +1,56 @@ +namespace AsmResolver.Symbols.Pdb.Types; + +/// +/// Represents a simple type referenced by a simple type index. +/// +public class SimpleType : CodeViewType +{ + /// + /// Constructs a new simple type based on the provided type index. + /// + /// The type index. + public SimpleType(uint typeIndex) + { + TypeIndex = typeIndex; + } + + /// + /// Constructs a new simple type with the provided type kind. + /// + /// The type kind. + public SimpleType(SimpleTypeKind kind) + : this((uint) kind) + { + } + + /// + /// Constructs a new simple type with the provided type kind and mode. + /// + /// The type kind. + /// The mode indicating the pointer specifiers added to the type. + public SimpleType(SimpleTypeKind kind, SimpleTypeMode mode) + : this((uint) kind | ((uint) mode << 8)) + { + } + + /// + public override CodeViewTypeKind TypeKind => CodeViewTypeKind.SimpleType; + + /// + /// Gets the kind of the simple type. + /// + public SimpleTypeKind Kind => (SimpleTypeKind) (TypeIndex & 0b1111_1111); + + /// + /// Gets the mode describing the pointer specifiers that are added to the simple type. + /// + public SimpleTypeMode Mode => (SimpleTypeMode) ((TypeIndex >> 8) & 0b1111); + + /// + /// Gets a value indicating whether the type is a pointer or not. + /// + public bool IsPointer => Mode != SimpleTypeMode.Direct; + + /// + public override string ToString() => $"{Kind} ({Mode})"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeKind.cs b/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeKind.cs new file mode 100644 index 000000000..d8881c805 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeKind.cs @@ -0,0 +1,250 @@ +namespace AsmResolver.Symbols.Pdb.Types; + +/// +/// Provides members defining all basic type kinds that can be used as a type index. +/// +/// +/// Reference: https://llvm.org/docs/PDB/TpiStream.html +/// +public enum SimpleTypeKind : uint +{ + /// + /// Indicates the type index indicates the absence of a specific type or type category. + /// + None = 0x0000, + + /// + /// Indicates the type index references the void type. + /// + Void = 0x0003, + + /// + /// Indicates the type index references a type that is not translated by CVPack. + /// + NotTranslated = 0x0007, + + /// + /// Indicates the type index references the OLE/COM HRESULT type. + /// + HResult = 0x0008, + + /// + /// Indicates the type index references the 8 bit signed character type. + /// + SignedCharacter = 0x0010, + + /// + /// Indicates the type index references the 8 bit unsigned character type. + /// + UnsignedCharacter = 0x0020, + + /// + /// Indicates the type index references the narrow character type. + /// + NarrowCharacter = 0x0070, + + /// + /// Indicates the type index references the wide character type. + /// + WideCharacter = 0x0071, + + /// + /// Indicates the type index references the char16_t type. + /// + Character16 = 0x007a, + + /// + /// Indicates the type index references the char32_t type. + /// + Character32 = 0x007b, + + /// + /// Indicates the type index references the char8_t type. + /// + Character8 = 0x007c, + + /// + /// Indicates the type index references the 8 bit signed int type. + /// + SByte = 0x0068, + + /// + /// Indicates the type index references the 8 bit unsigned int type. + /// + Byte = 0x0069, + + /// + /// Indicates the type index references the 16 bit signed type. + /// + Int16Short = 0x0011, + + /// + /// Indicates the type index references the 16 bit unsigned type. + /// + UInt16Short = 0x0021, + + /// + /// Indicates the type index references the 16 bit signed int type. + /// + Int16 = 0x0072, + + /// + /// Indicates the type index references the 16 bit unsigned int type. + /// + UInt16 = 0x0073, + + /// + /// Indicates the type index references the 32 bit signed type. + /// + Int32Long = 0x0012, + + /// + /// Indicates the type index references the 32 bit unsigned type. + /// + UInt32Long = 0x0022, + + /// + /// Indicates the type index references the 32 bit signed int type. + /// + Int32 = 0x0074, + + /// + /// Indicates the type index references the 32 bit unsigned int type. + /// + UInt32 = 0x0075, + + /// + /// Indicates the type index references the 64 bit signed type. + /// + Int64Quad = 0x0013, + + /// + /// Indicates the type index references the 64 bit unsigned type. + /// + UInt64Quad = 0x0023, + + /// + /// Indicates the type index references the 64 bit signed int type. + /// + Int64 = 0x0076, + + /// + /// Indicates the type index references the 64 bit unsigned int type. + /// + UInt64 = 0x0077, + + /// + /// Indicates the type index references the 128 bit signed int type. + /// + Int128Oct = 0x0014, + + /// + /// Indicates the type index references the 128 bit unsigned int type. + /// + UInt128Oct = 0x0024, + + /// + /// Indicates the type index references the 128 bit signed int type. + /// + Int128 = 0x0078, + + /// + /// Indicates the type index references the 128 bit unsigned int type. + /// + UInt128 = 0x0079, + + /// + /// Indicates the type index references the 16 bit real type. + /// + Float16 = 0x0046, + + /// + /// Indicates the type index references the 32 bit real type. + /// + Float32 = 0x0040, + + /// + /// Indicates the type index references the 32 bit PP real type. + /// + Float32PartialPrecision = 0x0045, + + /// + /// Indicates the type index references the 48 bit real type. + /// + Float48 = 0x0044, + + /// + /// Indicates the type index references the 64 bit real type. + /// + Float64 = 0x0041, + + /// + /// Indicates the type index references the 80 bit real type. + /// + Float80 = 0x0042, + + /// + /// Indicates the type index references the 128 bit real type. + /// + Float128 = 0x0043, + + /// + /// Indicates the type index references the 16 bit complex type. + /// + Complex16 = 0x0056, + + /// + /// Indicates the type index references the 32 bit complex type. + /// + Complex32 = 0x0050, + + /// + /// Indicates the type index references the 32 bit PP complex type. + /// + Complex32PartialPrecision = 0x0055, + + /// + /// Indicates the type index references the 48 bit complex type. + /// + Complex48 = 0x0054, + + /// + /// Indicates the type index references the 64 bit complex type. + /// + Complex64 = 0x0051, + + /// + /// Indicates the type index references the 80 bit complex type. + /// + Complex80 = 0x0052, + + /// + /// Indicates the type index references the 128 bit complex type. + /// + Complex128 = 0x0053, + + /// + /// Indicates the type index references the 8 bit boolean type. + /// + Boolean8 = 0x0030, + + /// + /// Indicates the type index references the 16 bit boolean type. + /// + Boolean16 = 0x0031, + + /// + /// Indicates the type index references the 32 bit boolean type. + /// + Boolean32 = 0x0032, + + /// + /// Indicates the type index references the 64 bit boolean type. + /// + Boolean64 = 0x0033, + + /// + /// Indicates the type index references the 128 bit boolean type. + /// + Boolean128 = 0x0034, +} diff --git a/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeMode.cs b/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeMode.cs new file mode 100644 index 000000000..f10a3ac0b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeMode.cs @@ -0,0 +1,50 @@ +namespace AsmResolver.Symbols.Pdb.Types; + +/// +/// Provides members defining all possible modes that a simple type in a PDB image can be set to. +/// +/// +/// Reference: https://llvm.org/docs/PDB/TpiStream.html +/// +public enum SimpleTypeMode +{ + /// + /// Indicates the type is not a pointer. + /// + Direct = 0, + + /// + /// Indicates the type is a near pointer. + /// + NearPointer = 1, + + /// + /// Indicates the type is a far pointer. + /// + FarPointer = 2, + + /// + /// Indicates the type is a huge pointer. + /// + HugePointer = 3, + + /// + /// Indicates the type is a 32 bit near pointer. + /// + NearPointer32 = 4, + + /// + /// Indicates the type is a 32 bit far pointer. + /// + FarPointer32 = 5, + + /// + /// Indicates the type is a 64 bit near pointer. + /// + NearPointer64 = 6, + + /// + /// Indicates the type is a 128 bit near pointer. + /// + NearPointer128 = 7, +} From 6434d0ba8e62d37274acf877d3d11eff8fda3635 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 20 Jul 2022 23:09:08 +0200 Subject: [PATCH 050/182] Extract serialized classes for symbol records. --- src/AsmResolver.Symbols.Pdb/PdbImage.cs | 16 ++++- .../PdbReaderContext.cs | 34 +++++++++++ .../PdbReaderParameters.cs | 33 +++++++++++ .../Records/CodeViewSymbol.cs | 8 ++- .../Records/PublicSymbol.cs | 35 ++++++----- .../Serialized/SerializedPublicSymbol.cs | 26 +++++++++ .../SerializedUserDefinedTypeSymbol.cs | 38 ++++++++++++ .../Records/UserDefinedTypeSymbol.cs | 58 +++++++++++++------ .../SerializedPdbImage.cs | 16 +++-- .../Types/CodeViewType.cs | 2 +- .../PdbImageTest.cs | 30 ++++++++++ 11 files changed, 255 insertions(+), 41 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs create mode 100644 src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index a36d93527..a3e473bfd 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -64,7 +64,21 @@ public IList Symbols /// /// The MSF file. /// The read PDB image. - public static PdbImage FromFile(MsfFile file) => new SerializedPdbImage(file); + public static PdbImage FromFile(MsfFile file) + { + return FromFile(file, new PdbReaderParameters(ThrowErrorListener.Instance)); + } + + /// + /// Loads a PDB image from the provided MSF file. + /// + /// The MSF file. + /// The parameters to use while reading the PDB image. + /// The read PDB image. + public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParameters) + { + return new SerializedPdbImage(file, readerParameters); + } /// /// Attempts to obtain a type record from the TPI or IPI stream based on its type index. diff --git a/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs b/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs new file mode 100644 index 000000000..9e2939315 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs @@ -0,0 +1,34 @@ +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides a context in which a PDB image reader exists in. This includes the PDB image as well as reader parameters. +/// +public class PdbReaderContext +{ + /// + /// Creates a new PDB reader context. + /// + /// The image for which the data is to be read. + /// The parameters used while reading the PDB image. + public PdbReaderContext(SerializedPdbImage parentImage, PdbReaderParameters parameters) + { + ParentImage = parentImage; + Parameters = parameters; + } + + /// + /// Gets the image for which the data is read. + /// + public SerializedPdbImage ParentImage + { + get; + } + + /// + /// Gets the parameters used for reading the data. + /// + public PdbReaderParameters Parameters + { + get; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs b/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs new file mode 100644 index 000000000..7368424b0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs @@ -0,0 +1,33 @@ +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides parameters for configuring the reading process of a PDB image. +/// +public class PdbReaderParameters +{ + /// + /// Creates new PDB reader parameters. + /// + public PdbReaderParameters() + : this(ThrowErrorListener.Instance) + { + } + + /// + /// Creates new PDB reader parameters with the provided error listener object. + /// + /// The object used for receiving parser errors. + public PdbReaderParameters(IErrorListener errorListener) + { + ErrorListener = errorListener; + } + + /// + /// Gets or sets the object responsible for receiving and processing parser errors. + /// + public IErrorListener ErrorListener + { + get; + set; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs index 02714bef0..9582d6caf 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs @@ -1,4 +1,5 @@ using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Records.Serialized; namespace AsmResolver.Symbols.Pdb.Records; @@ -18,9 +19,10 @@ public abstract class CodeViewSymbol /// /// Reads a single symbol record from the input stream. /// + /// The reading context in which the symbol is situated in. /// The input stream. /// The read symbol. - public static CodeViewSymbol FromReader(ref BinaryStreamReader reader) + public static CodeViewSymbol FromReader(PdbReaderContext context, ref BinaryStreamReader reader) { ushort length = reader.ReadUInt16(); var type = (CodeViewSymbolType) reader.ReadUInt16(); @@ -29,8 +31,8 @@ public static CodeViewSymbol FromReader(ref BinaryStreamReader reader) return type switch { - CodeViewSymbolType.Pub32 => PublicSymbol.FromReader(ref dataReader), - CodeViewSymbolType.Udt => UserDefinedTypeSymbol.FromReader(ref dataReader), + CodeViewSymbolType.Pub32 => new SerializedPublicSymbol(dataReader), + CodeViewSymbolType.Udt => new SerializedUserDefinedTypeSymbol(context, dataReader), _ => new UnknownSymbol(type, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs index 814bea47a..08515a42b 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs @@ -1,5 +1,3 @@ -using AsmResolver.IO; - namespace AsmResolver.Symbols.Pdb.Records; /// @@ -7,6 +5,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class PublicSymbol : CodeViewSymbol { + private readonly LazyVariable _name; + + /// + /// Initializes a new empty public symbol. + /// + protected PublicSymbol() + { + _name = new LazyVariable(GetName); + } + /// /// Creates a new public symbol. /// @@ -18,7 +26,7 @@ public PublicSymbol(ushort segment, uint offset, Utf8String name, PublicSymbolAt { Segment = segment; Offset = offset; - Name = name; + _name = new LazyVariable(name); Attributes = attributes; } @@ -97,19 +105,18 @@ public bool IsMsil /// public Utf8String Name { - get; - set; + get => _name.Value; + set => _name.Value = value; } - internal new static PublicSymbol FromReader(ref BinaryStreamReader reader) - { - var attributes = (PublicSymbolAttributes) reader.ReadUInt32(); - uint offset = reader.ReadUInt32(); - ushort segment = reader.ReadUInt16(); - var name = new Utf8String(reader.ReadToEnd()); - - return new PublicSymbol(segment, offset, name, attributes); - } + /// + /// Obtains the name of the public symbol. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; /// public override string ToString() => $"{CodeViewSymbolType}: [{Segment:X4}:{Offset:X8}] {Name}"; diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs new file mode 100644 index 000000000..149bfcb1d --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs @@ -0,0 +1,26 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedPublicSymbol : PublicSymbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a public symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedPublicSymbol(BinaryStreamReader reader) + { + Attributes = (PublicSymbolAttributes) reader.ReadUInt32(); + Offset = reader.ReadUInt32(); + Segment = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => new(_nameReader.Fork().ReadToEnd()); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs new file mode 100644 index 000000000..b708c3205 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs @@ -0,0 +1,38 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Types; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedUserDefinedTypeSymbol : UserDefinedTypeSymbol +{ + private readonly uint _typeIndex; + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a user-defined type symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _typeIndex = reader.ReadUInt32(); + _context = context; + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => new(_nameReader.Fork().ReadToEnd()); + + /// + protected override CodeViewType? GetSymbolType() + { + return _context.ParentImage.TryGetTypeRecord(_typeIndex, out var type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"User-defined type contains an invalid type index {_typeIndex:X4}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs index dcec87da4..761b5586a 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -1,21 +1,33 @@ -using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Types; namespace AsmResolver.Symbols.Pdb.Records; /// -/// Represents a user-defined type symbol in a PDB symbol stream. +/// Represents a user-defined type symbol in a PDB symbol stream, mapping a symbol to a type in the TPI stream. /// public class UserDefinedTypeSymbol : CodeViewSymbol { + private readonly LazyVariable _name; + private readonly LazyVariable _type; + + /// + /// Initializes a new empty user-defined type symbol. + /// + protected UserDefinedTypeSymbol() + { + _name = new LazyVariable(GetName); + _type = new LazyVariable(GetSymbolType); + } + /// /// Defines a new user-defined type. /// /// The name of the type. - /// The type index. - public UserDefinedTypeSymbol(Utf8String name, uint typeIndex) + /// The type. + public UserDefinedTypeSymbol(Utf8String name, CodeViewType type) { - Name = name; - TypeIndex = typeIndex; + _name = new LazyVariable(name); + _type = new LazyVariable(type); } /// @@ -26,27 +38,37 @@ public UserDefinedTypeSymbol(Utf8String name, uint typeIndex) /// public Utf8String Name { - get; - set; + get => _name.Value; + set => _name.Value = value; } /// /// Gets or sets the index associated to the type. /// - public uint TypeIndex + public CodeViewType Type { - get; - set; + get => _type.Value; + set => _type.Value = value; } - internal new static UserDefinedTypeSymbol FromReader(ref BinaryStreamReader reader) - { - uint typeIndex = reader.ReadUInt32(); - var name = new Utf8String(reader.ReadToEnd()); + /// + /// Obtains the new name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; - return new UserDefinedTypeSymbol(name, typeIndex); - } + /// + /// Obtains the type that is referenced by this symbol. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetSymbolType() => null; /// - public override string ToString() => $"{CodeViewSymbolType}: [{TypeIndex}] {Name}"; + public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name}"; } diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index 0e49fb129..e1b4f1f3f 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -25,7 +25,8 @@ public class SerializedPdbImage : PdbImage /// Interprets a PDB image from the provided MSF file. /// /// The MSF file to read from. - public SerializedPdbImage(MsfFile file) + /// The parameters to use while reading the PDB image. + public SerializedPdbImage(MsfFile file, PdbReaderParameters readerParameters) { _file = file; @@ -35,7 +36,14 @@ public SerializedPdbImage(MsfFile file) InfoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); DbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); TpiStream = TpiStream.FromReader(file.Streams[TpiStream.StreamIndex].CreateReader()); - } + + ReaderContext = new PdbReaderContext(this, readerParameters); + } + + internal PdbReaderContext ReaderContext + { + get; + } internal InfoStream InfoStream { @@ -75,7 +83,7 @@ public override bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out Co type = _types[typeIndex - TpiStream.TypeIndexBegin]; if (type is null && TpiStream.TryGetTypeRecordReader(typeIndex, out var reader)) { - type = CodeViewType.FromReader(this, reader); + type = CodeViewType.FromReader(ReaderContext, reader); type.TypeIndex = typeIndex; Interlocked.CompareExchange(ref _types[typeIndex - TpiStream.TypeIndexBegin], type, null); } @@ -99,7 +107,7 @@ protected override IList GetSymbols() var reader = _file.Streams[DbiStream.SymbolRecordStreamIndex].CreateReader(); while (reader.CanRead(sizeof(ushort) * 2)) - result.Add(CodeViewSymbol.FromReader(ref reader)); + result.Add(CodeViewSymbol.FromReader(ReaderContext, ref reader)); return result; } diff --git a/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs index 364b9d502..86aba5299 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs +++ b/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs @@ -24,7 +24,7 @@ public uint TypeIndex internal set; } - internal static CodeViewType FromReader(PdbImage owner, BinaryStreamReader reader) + internal static CodeViewType FromReader(PdbReaderContext context, BinaryStreamReader reader) { ushort length = reader.ReadUInt16(); var kind = (CodeViewTypeKind) reader.ReadUInt16(); diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs new file mode 100644 index 000000000..1c77548ba --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs @@ -0,0 +1,30 @@ +using AsmResolver.Symbols.Pdb.Types; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests; + +public class PdbImageTest +{ + [Theory] + [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] + [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] + public void SimpleTypeLookup(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) + { + var image = PdbImage.FromBytes(Properties.Resources.SimpleDllPdb); + + var type = Assert.IsAssignableFrom(image.GetTypeRecord(typeIndex)); + Assert.Equal(kind, type.Kind); + Assert.Equal(mode, type.Mode); + } + + [Fact] + public void SimpleTypeLookupTwiceShouldCache() + { + var image = PdbImage.FromBytes(Properties.Resources.SimpleDllPdb); + + var type = image.GetTypeRecord(0x00_75); + var type2 = image.GetTypeRecord(0x00_75); + + Assert.Same(type, type2); + } +} From 9c2f42d30242cfb9f6ad8d3da69596646d361e8a Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 20 Jul 2022 23:25:40 +0200 Subject: [PATCH 051/182] Move sample pdb image to fixture. --- .../Serialized/SerializedPublicSymbol.cs | 2 +- .../SerializedUserDefinedTypeSymbol.cs | 2 +- .../MockPdbFixture.cs | 9 ++++++ .../PdbImageTest.cs | 15 ++++++--- .../Records/PublicSymbolTest.cs | 21 +++++++++++++ .../Records/UserDefinedTypeTest.cs | 31 +++++++++++++++++++ .../Types/SimpleTypeTest.cs | 17 ++++++++++ 7 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Types/SimpleTypeTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs index 149bfcb1d..f1a815036 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs @@ -22,5 +22,5 @@ public SerializedPublicSymbol(BinaryStreamReader reader) } /// - protected override Utf8String GetName() => new(_nameReader.Fork().ReadToEnd()); + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); } diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs index b708c3205..b130bd11b 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs @@ -25,7 +25,7 @@ public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamRea } /// - protected override Utf8String GetName() => new(_nameReader.Fork().ReadToEnd()); + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// protected override CodeViewType? GetSymbolType() diff --git a/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs b/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs new file mode 100644 index 000000000..63a80f3aa --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs @@ -0,0 +1,9 @@ +namespace AsmResolver.Symbols.Pdb.Tests; + +public class MockPdbFixture +{ + public PdbImage SimplePdb + { + get; + } = PdbImage.FromBytes(Properties.Resources.SimpleDllPdb); +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs index 1c77548ba..23f6006e0 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs @@ -3,16 +3,21 @@ namespace AsmResolver.Symbols.Pdb.Tests; -public class PdbImageTest +public class PdbImageTest : IClassFixture { + private readonly MockPdbFixture _fixture; + + public PdbImageTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + [Theory] [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] public void SimpleTypeLookup(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) { - var image = PdbImage.FromBytes(Properties.Resources.SimpleDllPdb); - - var type = Assert.IsAssignableFrom(image.GetTypeRecord(typeIndex)); + var type = Assert.IsAssignableFrom(_fixture.SimplePdb.GetTypeRecord(typeIndex)); Assert.Equal(kind, type.Kind); Assert.Equal(mode, type.Mode); } @@ -20,7 +25,7 @@ public void SimpleTypeLookup(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode [Fact] public void SimpleTypeLookupTwiceShouldCache() { - var image = PdbImage.FromBytes(Properties.Resources.SimpleDllPdb); + var image = _fixture.SimplePdb; var type = image.GetTypeRecord(0x00_75); var type2 = image.GetTypeRecord(0x00_75); diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs new file mode 100644 index 000000000..dc16222e6 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs @@ -0,0 +1,21 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class PublicSymbolTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PublicSymbolTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + Assert.Equal("___enclave_config", _fixture.SimplePdb.Symbols.OfType().First().Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs new file mode 100644 index 000000000..565398068 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs @@ -0,0 +1,31 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using AsmResolver.Symbols.Pdb.Types; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class UserDefinedTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public UserDefinedTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + var udt = _fixture.SimplePdb.Symbols.OfType().First(); + Assert.Equal("UINT", udt.Name); + } + + [Fact] + public void Type() + { + var udt = _fixture.SimplePdb.Symbols.OfType().First(); + var type = Assert.IsAssignableFrom(udt.Type); + Assert.Equal(SimpleTypeKind.UInt32, type.Kind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Types/SimpleTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Types/SimpleTypeTest.cs new file mode 100644 index 000000000..ddd14af1c --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Types/SimpleTypeTest.cs @@ -0,0 +1,17 @@ +using AsmResolver.Symbols.Pdb.Types; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Types; + +public class SimpleTypeTest +{ + [Theory] + [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] + [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] + public void TypeIndexParsing(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) + { + var type = new SimpleType(typeIndex); + Assert.Equal(kind, type.Kind); + Assert.Equal(mode, type.Mode); + } +} From 4e898c5b7897f1051e4b20c719bdfab1eb0b8309 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 20 Jul 2022 23:40:47 +0200 Subject: [PATCH 052/182] Add S_CONSTANT read support. --- .../Records/CodeViewSymbol.cs | 1 + .../Records/ConstantSymbol.cs | 81 +++++++++++++++++++ .../Serialized/SerializedConstantSymbol.cs | 38 +++++++++ .../SerializedUserDefinedTypeSymbol.cs | 6 +- .../Records/ConstantTypeTest.cs | 34 ++++++++ 5 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs index 9582d6caf..5869c3acd 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs @@ -33,6 +33,7 @@ public static CodeViewSymbol FromReader(PdbReaderContext context, ref BinaryStre { CodeViewSymbolType.Pub32 => new SerializedPublicSymbol(dataReader), CodeViewSymbolType.Udt => new SerializedUserDefinedTypeSymbol(context, dataReader), + CodeViewSymbolType.Constant => new SerializedConstantSymbol(context, dataReader), _ => new UnknownSymbol(type, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs new file mode 100644 index 000000000..e41d50a36 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs @@ -0,0 +1,81 @@ +using AsmResolver.Symbols.Pdb.Types; + +namespace AsmResolver.Symbols.Pdb.Records; + +public class ConstantSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + private readonly LazyVariable _type; + + /// + /// Initializes a named constant + /// + protected ConstantSymbol() + { + _name = new LazyVariable(GetName); + _type = new LazyVariable(GetConstantType); + } + + /// + /// Defines a new named constant. + /// + /// The name of the type. + /// The type. + public ConstantSymbol(Utf8String name, CodeViewType type, ushort value) + { + _name = new LazyVariable(name); + _type = new LazyVariable(type); + Value = value; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Constant; + + /// + /// Gets or sets the value type of the constant. + /// + public CodeViewType Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the numerical value assigned to the constant. + /// + public ushort Value + { + get; + set; + } + + /// + /// Gets or sets the name of the constant. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the constant. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the value type of the constant. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetConstantType() => null; + + /// + public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name} = {Value}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs new file mode 100644 index 000000000..0a5101c26 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs @@ -0,0 +1,38 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Types; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedConstantSymbol : ConstantSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a constant symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedConstantSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _typeIndex = reader.ReadUInt32(); + Value = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewType? GetConstantType() + { + return _context.ParentImage.TryGetTypeRecord(_typeIndex, out var type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Constant contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs index b130bd11b..169fa7633 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs @@ -8,8 +8,8 @@ namespace AsmResolver.Symbols.Pdb.Records.Serialized; /// public class SerializedUserDefinedTypeSymbol : UserDefinedTypeSymbol { - private readonly uint _typeIndex; private readonly PdbReaderContext _context; + private readonly uint _typeIndex; private readonly BinaryStreamReader _nameReader; /// @@ -19,8 +19,8 @@ public class SerializedUserDefinedTypeSymbol : UserDefinedTypeSymbol /// The input stream to read from. public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamReader reader) { - _typeIndex = reader.ReadUInt32(); _context = context; + _typeIndex = reader.ReadUInt32(); _nameReader = reader; } @@ -33,6 +33,6 @@ public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamRea return _context.ParentImage.TryGetTypeRecord(_typeIndex, out var type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( - $"User-defined type contains an invalid type index {_typeIndex:X4}."); + $"User-defined type contains an invalid type index {_typeIndex:X8}."); } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs new file mode 100644 index 000000000..fa7f78194 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs @@ -0,0 +1,34 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using AsmResolver.Symbols.Pdb.Types; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class ConstantTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ConstantTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + Assert.Equal("JOB_OBJECT_IO_RATE_CONTROL_STANDALONE_VOLUME", _fixture.SimplePdb.Symbols.OfType().First().Name); + } + + [Fact] + public void Type() + { + Assert.Equal(CodeViewTypeKind.Enum, _fixture.SimplePdb.Symbols.OfType().First().Type.TypeKind); + } + + [Fact] + public void Value() + { + Assert.Equal(2, _fixture.SimplePdb.Symbols.OfType().First().Value); + } +} From 63d0f9671830a0580807c1f618a6ad28723c774b Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 21 Jul 2022 00:22:13 +0200 Subject: [PATCH 053/182] Add basic LF_ENUM read support. --- .../Serialized/SerializedConstantSymbol.cs | 1 + .../Records/UserDefinedTypeSymbol.cs | 2 +- .../SerializedPdbImage.cs | 3 +- .../Types/CodeViewType.cs | 16 +++- src/AsmResolver.Symbols.Pdb/Types/EnumType.cs | 86 +++++++++++++++++++ .../Types/Serialized/SerializedEnumType.cs | 41 +++++++++ .../Types/SimpleType.cs | 6 +- .../Types/StructureAttributes.cs | 80 +++++++++++++++++ .../Types/UnknownCodeViewType.cs | 12 +++ .../PdbImageTest.cs | 2 + 10 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Types/EnumType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Types/Serialized/SerializedEnumType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Types/StructureAttributes.cs diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs index 0a5101c26..b6dbf3412 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs @@ -15,6 +15,7 @@ public class SerializedConstantSymbol : ConstantSymbol /// /// Reads a constant symbol from the provided input stream. /// + /// The reading context in which the symbol is situated in. /// The input stream to read from. public SerializedConstantSymbol(PdbReaderContext context, BinaryStreamReader reader) { diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs index 761b5586a..31d4b4fb3 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -63,7 +63,7 @@ public CodeViewType Type /// /// Obtains the type that is referenced by this symbol. /// - /// The name. + /// The type. /// /// This method is called upon initialization of the property. /// diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index e1b4f1f3f..97b2d1202 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -83,8 +83,7 @@ public override bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out Co type = _types[typeIndex - TpiStream.TypeIndexBegin]; if (type is null && TpiStream.TryGetTypeRecordReader(typeIndex, out var reader)) { - type = CodeViewType.FromReader(ReaderContext, reader); - type.TypeIndex = typeIndex; + type = CodeViewType.FromReader(ReaderContext, typeIndex, ref reader); Interlocked.CompareExchange(ref _types[typeIndex - TpiStream.TypeIndexBegin], type, null); } diff --git a/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs index 86aba5299..b2622c883 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs +++ b/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs @@ -1,4 +1,5 @@ using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Types.Serialized; namespace AsmResolver.Symbols.Pdb.Types; @@ -7,6 +8,11 @@ namespace AsmResolver.Symbols.Pdb.Types; /// public abstract class CodeViewType { + protected CodeViewType(uint typeIndex) + { + TypeIndex = typeIndex; + } + /// /// Gets the type kind this record encodes. /// @@ -24,14 +30,18 @@ public uint TypeIndex internal set; } - internal static CodeViewType FromReader(PdbReaderContext context, BinaryStreamReader reader) + internal static CodeViewType FromReader(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) { ushort length = reader.ReadUInt16(); - var kind = (CodeViewTypeKind) reader.ReadUInt16(); + var dataReader = reader.Fork(); + reader.Offset += length; + + var kind = (CodeViewTypeKind) dataReader.ReadUInt16(); return kind switch { - _ => new UnknownCodeViewType(kind, reader.ReadToEnd()) + CodeViewTypeKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), + _ => new UnknownCodeViewType(kind, dataReader.ReadToEnd()) }; } } diff --git a/src/AsmResolver.Symbols.Pdb/Types/EnumType.cs b/src/AsmResolver.Symbols.Pdb/Types/EnumType.cs new file mode 100644 index 000000000..892892520 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/EnumType.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Types; + +public class EnumType : CodeViewType +{ + private readonly LazyVariable _name; + private readonly LazyVariable _type; + private IList? _fields; + + protected EnumType(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + _type = new LazyVariable(GetEnumUnderlyingType); + } + + public EnumType(Utf8String name, CodeViewType underlyingType) + : base(0) + { + _name = new LazyVariable(name); + _type = new LazyVariable(underlyingType); + } + + /// + public override CodeViewTypeKind TypeKind => CodeViewTypeKind.Enum; + + public StructureAttributes StructureAttributes + { + get; + set; + } + + public CodeViewType EnumUnderlyingType + { + get => _type.Value; + set => _type.Value = value; + } + + public IList Fields + { + get + { + if (_fields is null) + Interlocked.CompareExchange(ref _fields, GetFields(), null); + return _fields; + } + } + + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the new name of the enum type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the type that the enum is based on. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetEnumUnderlyingType() => null; + + /// + /// Obtains the fields defined in the enum type. + /// + /// The fields. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetFields() => new List(); + + /// + public override string ToString() => Name; +} diff --git a/src/AsmResolver.Symbols.Pdb/Types/Serialized/SerializedEnumType.cs b/src/AsmResolver.Symbols.Pdb/Types/Serialized/SerializedEnumType.cs new file mode 100644 index 000000000..7c5ee86c1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/Serialized/SerializedEnumType.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Types.Serialized; + +public class SerializedEnumType : EnumType +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _underlyingType; + private readonly uint _fieldIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a constant symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _underlyingType = reader.ReadUInt32(); + _fieldIndex = reader.ReadUInt32(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewType? GetEnumUnderlyingType() + { + return _context.ParentImage.TryGetTypeRecord(_underlyingType, out var type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs b/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs index 7ffc09dda..e91aeb147 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs +++ b/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs @@ -10,8 +10,8 @@ public class SimpleType : CodeViewType /// /// The type index. public SimpleType(uint typeIndex) + : base(typeIndex) { - TypeIndex = typeIndex; } /// @@ -19,7 +19,7 @@ public SimpleType(uint typeIndex) /// /// The type kind. public SimpleType(SimpleTypeKind kind) - : this((uint) kind) + : base((uint) kind) { } @@ -29,7 +29,7 @@ public SimpleType(SimpleTypeKind kind) /// The type kind. /// The mode indicating the pointer specifiers added to the type. public SimpleType(SimpleTypeKind kind, SimpleTypeMode mode) - : this((uint) kind | ((uint) mode << 8)) + : base((uint) kind | ((uint) mode << 8)) { } diff --git a/src/AsmResolver.Symbols.Pdb/Types/StructureAttributes.cs b/src/AsmResolver.Symbols.Pdb/Types/StructureAttributes.cs new file mode 100644 index 000000000..1ce006e0b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Types/StructureAttributes.cs @@ -0,0 +1,80 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Types; + +/// +/// Provides members defining all possible attributes that can be assigned to a structure, class or enum type symbol. +/// +[Flags] +public enum StructureAttributes : ushort +{ + /// + /// Indicates the structure is packed. + /// + Packed = 0b00000000_00000001, + + /// + /// Indicates the structure defines constructors or destructors. + /// + Ctor = 0b00000000_00000010, + + /// + /// Indicates the structure defines overloaded operators. + /// + OvlOps = 0b00000000_00000100, + + /// + /// Indicates the structure is a nested class. + /// + IsNested = 0b00000000_00001000, + + /// + /// Indicates the structure defines nested types. + /// + CNested = 0b00000000_00010000, + + /// + /// Indicates the structure defines an overloaded assignment (=) operator. + /// + OpAssign = 0b00000000_00100000, + + /// + /// Indicates the structure defines casting methods. + /// + OpCast = 0b00000000_01000000, + + /// + /// Indicates the structure true is a forward reference. + /// + FwdRef = 0b00000000_10000000, + + /// + /// Indicates the structure is a scoped definition. + /// + Scoped = 0b00000001_00000000, + + /// + /// Indicates the structure has a decorated name following the regular naming conventions. + /// + HasUniqueName = 0b00000010_00000000, + + /// + /// Indicates the structure cannot be used as a base class. + /// + Sealed = 0b00000100_00000000, + + /// + /// Defines the mask for the floating point type that is used within this structure. + /// + HfaMask = 0b00011000_00000000, + + /// + /// Indicates the structure is an intrinsic type. + /// + Intrinsic = 0b00100000_00000000, + + /// + /// Defines the mask for the MoCOM type kind. + /// + MoComMask = 0b11000000_00000000, +} diff --git a/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs index 3f0215798..9ee6ba1a2 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs +++ b/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs @@ -11,6 +11,18 @@ public class UnknownCodeViewType : CodeViewType /// The type of symbol. /// The raw data stored in the record. public UnknownCodeViewType(CodeViewTypeKind typeKind, byte[] data) + : this(0, typeKind, data) + { + } + + /// + /// Creates a new unknown type record. + /// + /// The type index to assign to the type + /// The type of symbol. + /// The raw data stored in the record. + internal UnknownCodeViewType(uint typeIndex, CodeViewTypeKind typeKind, byte[] data) + : base(typeIndex) { TypeKind = typeKind; Data = data; diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs index 23f6006e0..91264c9e2 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs @@ -1,3 +1,5 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; using AsmResolver.Symbols.Pdb.Types; using Xunit; From e5592d0a6d9f156e860afeecaa79d6b0b0529467 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 21 Jul 2022 20:43:36 +0200 Subject: [PATCH 054/182] Rename Types to Leaves. --- .../CodeViewLeaf.cs} | 16 +++++++------- .../CodeViewLeafKind.cs} | 4 ++-- .../Leaves/CodeViewType.cs | 10 +++++++++ .../{Types => Leaves}/EnumType.cs | 22 +++++++++---------- .../Serialized/SerializedEnumType.cs | 9 ++++---- .../{Types => Leaves}/SimpleType.cs | 4 ++-- .../{Types => Leaves}/SimpleTypeKind.cs | 2 +- .../{Types => Leaves}/SimpleTypeMode.cs | 2 +- .../{Types => Leaves}/StructureAttributes.cs | 2 +- .../{Types => Leaves}/UnknownCodeViewType.cs | 20 ++++++++--------- src/AsmResolver.Symbols.Pdb/PdbImage.cs | 8 +++---- .../Records/ConstantSymbol.cs | 2 +- .../Serialized/SerializedConstantSymbol.cs | 4 ++-- .../SerializedUserDefinedTypeSymbol.cs | 4 ++-- .../Records/UserDefinedTypeSymbol.cs | 2 +- .../SerializedPdbImage.cs | 12 +++++----- .../{Types => Leaves}/SimpleTypeTest.cs | 4 ++-- .../PdbImageTest.cs | 10 ++++----- .../Records/ConstantTypeTest.cs | 4 ++-- .../Records/UserDefinedTypeTest.cs | 2 +- 20 files changed, 75 insertions(+), 68 deletions(-) rename src/AsmResolver.Symbols.Pdb/{Types/CodeViewType.cs => Leaves/CodeViewLeaf.cs} (65%) rename src/AsmResolver.Symbols.Pdb/{Types/CodeViewTypeKind.cs => Leaves/CodeViewLeafKind.cs} (98%) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs rename src/AsmResolver.Symbols.Pdb/{Types => Leaves}/EnumType.cs (73%) rename src/AsmResolver.Symbols.Pdb/{Types => Leaves}/Serialized/SerializedEnumType.cs (85%) rename src/AsmResolver.Symbols.Pdb/{Types => Leaves}/SimpleType.cs (93%) rename src/AsmResolver.Symbols.Pdb/{Types => Leaves}/SimpleTypeKind.cs (99%) rename src/AsmResolver.Symbols.Pdb/{Types => Leaves}/SimpleTypeMode.cs (96%) rename src/AsmResolver.Symbols.Pdb/{Types => Leaves}/StructureAttributes.cs (98%) rename src/AsmResolver.Symbols.Pdb/{Types => Leaves}/UnknownCodeViewType.cs (59%) rename test/AsmResolver.Symbols.Pdb.Tests/{Types => Leaves}/SimpleTypeTest.cs (83%) diff --git a/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs similarity index 65% rename from src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index b2622c883..911d579f9 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/CodeViewType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -1,14 +1,14 @@ using AsmResolver.IO; -using AsmResolver.Symbols.Pdb.Types.Serialized; +using AsmResolver.Symbols.Pdb.Leaves.Serialized; -namespace AsmResolver.Symbols.Pdb.Types; +namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a single type record in a TPI or IPI stream. /// -public abstract class CodeViewType +public abstract class CodeViewLeaf { - protected CodeViewType(uint typeIndex) + protected CodeViewLeaf(uint typeIndex) { TypeIndex = typeIndex; } @@ -16,7 +16,7 @@ protected CodeViewType(uint typeIndex) /// /// Gets the type kind this record encodes. /// - public abstract CodeViewTypeKind TypeKind + public abstract CodeViewLeafKind LeafKind { get; } @@ -30,17 +30,17 @@ public uint TypeIndex internal set; } - internal static CodeViewType FromReader(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) { ushort length = reader.ReadUInt16(); var dataReader = reader.Fork(); reader.Offset += length; - var kind = (CodeViewTypeKind) dataReader.ReadUInt16(); + var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { - CodeViewTypeKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), + CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), _ => new UnknownCodeViewType(kind, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs similarity index 98% rename from src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs index 905649af4..cf156db17 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/CodeViewTypeKind.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs @@ -1,9 +1,9 @@ -namespace AsmResolver.Symbols.Pdb.Types; +namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Provides members defining all possible type record kinds that can be stored in a TPI or IPI stream. /// -public enum CodeViewTypeKind : ushort +public enum CodeViewLeafKind : ushort { #pragma warning disable CS1591 /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs new file mode 100644 index 000000000..2ac9af947 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs @@ -0,0 +1,10 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +public abstract class CodeViewType : CodeViewLeaf +{ + /// + protected CodeViewType(uint typeIndex) + : base(typeIndex) + { + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Types/EnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs similarity index 73% rename from src/AsmResolver.Symbols.Pdb/Types/EnumType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs index 892892520..78226bfd4 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/EnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs @@ -1,30 +1,30 @@ using System.Collections.Generic; using System.Threading; -namespace AsmResolver.Symbols.Pdb.Types; +namespace AsmResolver.Symbols.Pdb.Leaves; public class EnumType : CodeViewType { private readonly LazyVariable _name; - private readonly LazyVariable _type; - private IList? _fields; + private readonly LazyVariable _type; + private IList? _fields; protected EnumType(uint typeIndex) : base(typeIndex) { _name = new LazyVariable(GetName); - _type = new LazyVariable(GetEnumUnderlyingType); + _type = new LazyVariable(GetEnumUnderlyingType); } - public EnumType(Utf8String name, CodeViewType underlyingType) + public EnumType(Utf8String name, CodeViewLeaf underlyingType) : base(0) { _name = new LazyVariable(name); - _type = new LazyVariable(underlyingType); + _type = new LazyVariable(underlyingType); } /// - public override CodeViewTypeKind TypeKind => CodeViewTypeKind.Enum; + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enum; public StructureAttributes StructureAttributes { @@ -32,13 +32,13 @@ public StructureAttributes StructureAttributes set; } - public CodeViewType EnumUnderlyingType + public CodeViewLeaf EnumUnderlyingType { get => _type.Value; set => _type.Value = value; } - public IList Fields + public IList Fields { get { @@ -70,7 +70,7 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetEnumUnderlyingType() => null; + protected virtual CodeViewLeaf? GetEnumUnderlyingType() => null; /// /// Obtains the fields defined in the enum type. @@ -79,7 +79,7 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual IList GetFields() => new List(); + protected virtual IList GetFields() => new List(); /// public override string ToString() => Name; diff --git a/src/AsmResolver.Symbols.Pdb/Types/Serialized/SerializedEnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs similarity index 85% rename from src/AsmResolver.Symbols.Pdb/Types/Serialized/SerializedEnumType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs index 7c5ee86c1..4e812775f 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/Serialized/SerializedEnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; using AsmResolver.IO; -namespace AsmResolver.Symbols.Pdb.Types.Serialized; +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; public class SerializedEnumType : EnumType { @@ -31,11 +30,11 @@ public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStream protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetEnumUnderlyingType() + protected override CodeViewLeaf? GetEnumUnderlyingType() { - return _context.ParentImage.TryGetTypeRecord(_underlyingType, out var type) + return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var type) ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleType.cs similarity index 93% rename from src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/SimpleType.cs index e91aeb147..9efefe20f 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/SimpleType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleType.cs @@ -1,4 +1,4 @@ -namespace AsmResolver.Symbols.Pdb.Types; +namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a simple type referenced by a simple type index. @@ -34,7 +34,7 @@ public SimpleType(SimpleTypeKind kind, SimpleTypeMode mode) } /// - public override CodeViewTypeKind TypeKind => CodeViewTypeKind.SimpleType; + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.SimpleType; /// /// Gets the kind of the simple type. diff --git a/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs similarity index 99% rename from src/AsmResolver.Symbols.Pdb/Types/SimpleTypeKind.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs index d8881c805..77cbbff33 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeKind.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs @@ -1,4 +1,4 @@ -namespace AsmResolver.Symbols.Pdb.Types; +namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Provides members defining all basic type kinds that can be used as a type index. diff --git a/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeMode.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs similarity index 96% rename from src/AsmResolver.Symbols.Pdb/Types/SimpleTypeMode.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs index f10a3ac0b..7dff91423 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/SimpleTypeMode.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs @@ -1,4 +1,4 @@ -namespace AsmResolver.Symbols.Pdb.Types; +namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Provides members defining all possible modes that a simple type in a PDB image can be set to. diff --git a/src/AsmResolver.Symbols.Pdb/Types/StructureAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs similarity index 98% rename from src/AsmResolver.Symbols.Pdb/Types/StructureAttributes.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs index 1ce006e0b..64f582c6b 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/StructureAttributes.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs @@ -1,6 +1,6 @@ using System; -namespace AsmResolver.Symbols.Pdb.Types; +namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Provides members defining all possible attributes that can be assigned to a structure, class or enum type symbol. diff --git a/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewType.cs similarity index 59% rename from src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewType.cs index 9ee6ba1a2..7426b39b4 100644 --- a/src/AsmResolver.Symbols.Pdb/Types/UnknownCodeViewType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewType.cs @@ -1,17 +1,17 @@ -namespace AsmResolver.Symbols.Pdb.Types; +namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents an unknown or unsupported CodeView type record. /// -public class UnknownCodeViewType : CodeViewType +public class UnknownCodeViewType : CodeViewLeaf { /// /// Creates a new unknown type record. /// - /// The type of symbol. + /// The type of symbol. /// The raw data stored in the record. - public UnknownCodeViewType(CodeViewTypeKind typeKind, byte[] data) - : this(0, typeKind, data) + public UnknownCodeViewType(CodeViewLeafKind leafKind, byte[] data) + : this(0, leafKind, data) { } @@ -19,17 +19,17 @@ public UnknownCodeViewType(CodeViewTypeKind typeKind, byte[] data) /// Creates a new unknown type record. /// /// The type index to assign to the type - /// The type of symbol. + /// The type of symbol. /// The raw data stored in the record. - internal UnknownCodeViewType(uint typeIndex, CodeViewTypeKind typeKind, byte[] data) + internal UnknownCodeViewType(uint typeIndex, CodeViewLeafKind leafKind, byte[] data) : base(typeIndex) { - TypeKind = typeKind; + LeafKind = leafKind; Data = data; } /// - public override CodeViewTypeKind TypeKind + public override CodeViewLeafKind LeafKind { get; } @@ -43,5 +43,5 @@ public byte[] Data } /// - public override string ToString() => $"{TypeKind.ToString()} ({Data.Length.ToString()} bytes)"; + public override string ToString() => $"{LeafKind.ToString()} ({Data.Length.ToString()} bytes)"; } diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index a3e473bfd..10cd6eaea 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -4,9 +4,9 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves; using AsmResolver.Symbols.Pdb.Msf; using AsmResolver.Symbols.Pdb.Records; -using AsmResolver.Symbols.Pdb.Types; namespace AsmResolver.Symbols.Pdb; @@ -86,7 +86,7 @@ public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParamete /// The type index. /// The resolved type. /// true if the type was found, false otherwise. - public virtual bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewType? type) + public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? type) { typeIndex &= 0x7fffffff; if (typeIndex < 0x1000) @@ -105,9 +105,9 @@ public virtual bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out Cod /// The type index. /// The resolved type. /// Occurs when the type index is invalid. - public CodeViewType GetTypeRecord(uint typeIndex) + public CodeViewLeaf GetLeafRecord(uint typeIndex) { - if (!TryGetTypeRecord(typeIndex, out var type)) + if (!TryGetLeafRecord(typeIndex, out var type)) throw new ArgumentException("Invalid type index."); return type; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs index e41d50a36..aeafc45ee 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs @@ -1,4 +1,4 @@ -using AsmResolver.Symbols.Pdb.Types; +using AsmResolver.Symbols.Pdb.Leaves; namespace AsmResolver.Symbols.Pdb.Records; diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs index b6dbf3412..10d5db4c3 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs @@ -1,5 +1,5 @@ using AsmResolver.IO; -using AsmResolver.Symbols.Pdb.Types; +using AsmResolver.Symbols.Pdb.Leaves; namespace AsmResolver.Symbols.Pdb.Records.Serialized; @@ -31,7 +31,7 @@ public SerializedConstantSymbol(PdbReaderContext context, BinaryStreamReader rea /// protected override CodeViewType? GetConstantType() { - return _context.ParentImage.TryGetTypeRecord(_typeIndex, out var type) + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewType type ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Constant contains an invalid type index {_typeIndex:X8}."); diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs index 169fa7633..c259ddd65 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs @@ -1,5 +1,5 @@ using AsmResolver.IO; -using AsmResolver.Symbols.Pdb.Types; +using AsmResolver.Symbols.Pdb.Leaves; namespace AsmResolver.Symbols.Pdb.Records.Serialized; @@ -30,7 +30,7 @@ public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamRea /// protected override CodeViewType? GetSymbolType() { - return _context.ParentImage.TryGetTypeRecord(_typeIndex, out var type) + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewType type ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"User-defined type contains an invalid type index {_typeIndex:X8}."); diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs index 31d4b4fb3..8f33b3922 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -1,4 +1,4 @@ -using AsmResolver.Symbols.Pdb.Types; +using AsmResolver.Symbols.Pdb.Leaves; namespace AsmResolver.Symbols.Pdb.Records; diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index 97b2d1202..38cf32db9 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; +using AsmResolver.Symbols.Pdb.Leaves; using AsmResolver.Symbols.Pdb.Metadata.Dbi; using AsmResolver.Symbols.Pdb.Metadata.Info; using AsmResolver.Symbols.Pdb.Metadata.Tpi; using AsmResolver.Symbols.Pdb.Msf; using AsmResolver.Symbols.Pdb.Records; -using AsmResolver.Symbols.Pdb.Types; namespace AsmResolver.Symbols.Pdb; @@ -19,7 +19,7 @@ public class SerializedPdbImage : PdbImage { private const int MinimalRequiredStreamCount = 5; private readonly MsfFile _file; - private CodeViewType?[]? _types; + private CodeViewLeaf?[]? _types; /// /// Interprets a PDB image from the provided MSF file. @@ -66,15 +66,15 @@ private void EnsureTypeArrayInitialized() if (_types is null) { Interlocked.CompareExchange(ref _types, - new CodeViewType?[TpiStream.TypeIndexEnd - TpiStream.TypeIndexBegin], null); + new CodeViewLeaf?[TpiStream.TypeIndexEnd - TpiStream.TypeIndexBegin], null); } } /// - public override bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewType? type) + public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? type) { if (typeIndex < TpiStream.TypeIndexBegin) - return base.TryGetTypeRecord(typeIndex, out type); + return base.TryGetLeafRecord(typeIndex, out type); EnsureTypeArrayInitialized(); @@ -83,7 +83,7 @@ public override bool TryGetTypeRecord(uint typeIndex, [NotNullWhen(true)] out Co type = _types[typeIndex - TpiStream.TypeIndexBegin]; if (type is null && TpiStream.TryGetTypeRecordReader(typeIndex, out var reader)) { - type = CodeViewType.FromReader(ReaderContext, typeIndex, ref reader); + type = CodeViewLeaf.FromReader(ReaderContext, typeIndex, ref reader); Interlocked.CompareExchange(ref _types[typeIndex - TpiStream.TypeIndexBegin], type, null); } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Types/SimpleTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeTest.cs similarity index 83% rename from test/AsmResolver.Symbols.Pdb.Tests/Types/SimpleTypeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeTest.cs index ddd14af1c..9b0e44f8b 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Types/SimpleTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeTest.cs @@ -1,7 +1,7 @@ -using AsmResolver.Symbols.Pdb.Types; +using AsmResolver.Symbols.Pdb.Leaves; using Xunit; -namespace AsmResolver.Symbols.Pdb.Tests.Types; +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; public class SimpleTypeTest { diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs index 91264c9e2..d166893a3 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs @@ -1,6 +1,4 @@ -using System.Linq; -using AsmResolver.Symbols.Pdb.Records; -using AsmResolver.Symbols.Pdb.Types; +using AsmResolver.Symbols.Pdb.Leaves; using Xunit; namespace AsmResolver.Symbols.Pdb.Tests; @@ -19,7 +17,7 @@ public PdbImageTest(MockPdbFixture fixture) [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] public void SimpleTypeLookup(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) { - var type = Assert.IsAssignableFrom(_fixture.SimplePdb.GetTypeRecord(typeIndex)); + var type = Assert.IsAssignableFrom(_fixture.SimplePdb.GetLeafRecord(typeIndex)); Assert.Equal(kind, type.Kind); Assert.Equal(mode, type.Mode); } @@ -29,8 +27,8 @@ public void SimpleTypeLookupTwiceShouldCache() { var image = _fixture.SimplePdb; - var type = image.GetTypeRecord(0x00_75); - var type2 = image.GetTypeRecord(0x00_75); + var type = image.GetLeafRecord(0x00_75); + var type2 = image.GetLeafRecord(0x00_75); Assert.Same(type, type2); } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs index fa7f78194..a9cfdfbf6 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs @@ -1,6 +1,6 @@ using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; using AsmResolver.Symbols.Pdb.Records; -using AsmResolver.Symbols.Pdb.Types; using Xunit; namespace AsmResolver.Symbols.Pdb.Tests.Records; @@ -23,7 +23,7 @@ public void Name() [Fact] public void Type() { - Assert.Equal(CodeViewTypeKind.Enum, _fixture.SimplePdb.Symbols.OfType().First().Type.TypeKind); + Assert.Equal(CodeViewLeafKind.Enum, _fixture.SimplePdb.Symbols.OfType().First().Type.LeafKind); } [Fact] diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs index 565398068..a25234fb1 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs @@ -1,6 +1,6 @@ using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; using AsmResolver.Symbols.Pdb.Records; -using AsmResolver.Symbols.Pdb.Types; using Xunit; namespace AsmResolver.Symbols.Pdb.Tests.Records; From d81e0a4e482f203fe64264fe4341340ecb901c24 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 27 Jul 2022 22:29:50 +0200 Subject: [PATCH 055/182] Add read support for LF_FIELDLIST and LF_ENUMERATE records. --- .../Leaves/CodeViewField.cs | 10 ++++ .../Leaves/CodeViewFieldAttributes.cs | 21 +++++++ .../Leaves/CodeViewLeaf.cs | 13 ++++- .../Leaves/EnumType.cs | 6 +- .../Leaves/EnumerateLeaf.cs | 56 +++++++++++++++++++ .../Leaves/FieldList.cs | 34 +++++++++++ .../Leaves/Serialized/SerializedEnumType.cs | 16 +++++- .../Serialized/SerializedEnumerateLeaf.cs | 35 ++++++++++++ .../Leaves/Serialized/SerializedFieldList.cs | 49 ++++++++++++++++ src/AsmResolver/IO/BinaryStreamReader.cs | 36 +++++++----- 10 files changed, 255 insertions(+), 21 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/EnumerateLeaf.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateLeaf.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs new file mode 100644 index 000000000..9148e9e5c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -0,0 +1,10 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +public abstract class CodeViewField : CodeViewLeaf +{ + /// + protected CodeViewField(uint typeIndex) + : base(typeIndex) + { + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs new file mode 100644 index 000000000..0c63f7e0b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs @@ -0,0 +1,21 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +[Flags] +public enum CodeViewFieldAttributes : ushort +{ + None = 0b00000000_00000000, + Private = 0b00000000_00000001, + Protected = 0b00000000_00000010, + Public = 0b00000000_00000011, + AccessMask = 0b00000000_00000011, + + MethodPropertiesMask = 0b00000000_00011100, + + Pseudo = 0b00000000_00100000, + NoInherit = 0b00000000_01000000, + NoConstruct = 0b00000000_10000000, + CompilerGenerated = 0b00000001_00000000, + Sealed = 0b00000010_00000000 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 911d579f9..94692a7fa 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -36,11 +36,20 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex var dataReader = reader.Fork(); reader.Offset += length; - var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); + return FromReaderNoHeader(context, typeIndex, ref dataReader); + } + internal static CodeViewLeaf FromReaderNoHeader( + PdbReaderContext context, + uint typeIndex, + ref BinaryStreamReader dataReader) + { + var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { - CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), + CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, ref dataReader), + CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, ref dataReader), + CodeViewLeafKind.Enumerate => new SerializedEnumerateLeaf(context, typeIndex, ref dataReader), _ => new UnknownCodeViewType(kind, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs index 78226bfd4..349994148 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs @@ -7,7 +7,7 @@ public class EnumType : CodeViewType { private readonly LazyVariable _name; private readonly LazyVariable _type; - private IList? _fields; + private IList? _fields; protected EnumType(uint typeIndex) : base(typeIndex) @@ -38,7 +38,7 @@ public CodeViewLeaf EnumUnderlyingType set => _type.Value = value; } - public IList Fields + public IList Fields { get { @@ -79,7 +79,7 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual IList GetFields() => new List(); + protected virtual IList GetFields() => new List(); /// public override string ToString() => Name; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateLeaf.cs new file mode 100644 index 000000000..9d99c8dba --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateLeaf.cs @@ -0,0 +1,56 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +public class EnumerateLeaf : CodeViewField +{ + private readonly LazyVariable _name; + private readonly LazyVariable _value; + + protected EnumerateLeaf(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + _value = new LazyVariable(GetValue); + } + + public EnumerateLeaf(Utf8String name, object value, CodeViewFieldAttributes attributes) + : base(0) + { + _name = new LazyVariable(name); + _value = new LazyVariable(value); + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enumerate; + + /// + /// Gets or sets the name of the enumerate field. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Gets or sets the constant value assigned to the field. + /// + public object Value + { + get => _value.Value; + set => _value.Value = value; + } + + public CodeViewFieldAttributes Attributes + { + get; + set; + } + + protected virtual Utf8String GetName() => Utf8String.Empty; + + protected virtual object? GetValue() => null; + + /// + public override string ToString() => $"{Name} = {Value}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs new file mode 100644 index 000000000..4b080a9c6 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +public class FieldList : CodeViewLeaf +{ + private IList? _fields; + + protected FieldList(uint typeIndex) + : base(typeIndex) + { + } + + public FieldList() + : base(0) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList; + + public IList Fields + { + get + { + if (_fields is null) + Interlocked.CompareExchange(ref _fields, GetFields(), null); + return _fields; + } + } + + protected virtual IList GetFields() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs index 4e812775f..5a61a1485 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using AsmResolver.IO; namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; @@ -15,7 +16,7 @@ public class SerializedEnumType : EnumType /// /// The reading context in which the symbol is situated in. /// The input stream to read from. - public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedEnumType(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -37,4 +38,17 @@ public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStream : _context.Parameters.ErrorListener.BadImageAndReturn( $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}."); } + + /// + protected override IList GetFields() + { + if (!_context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) || leaf is not SerializedFieldList list) + { + _context.Parameters.ErrorListener.BadImage( + $"Enum type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + return new List(); + } + + return list.Fields; + } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateLeaf.cs new file mode 100644 index 000000000..14c2e162a --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateLeaf.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +public class SerializedEnumerateLeaf : EnumerateLeaf +{ + private readonly BinaryStreamReader _nameReader; + + public SerializedEnumerateLeaf(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + var kind = (CodeViewLeafKind) reader.ReadUInt16(); + + // We need to eagerly initialize the value because it is the only way to know how large the leaf is. + Value = kind switch + { + < CodeViewLeafKind.Numeric => (object) (uint) kind, + CodeViewLeafKind.Char => (char) reader.ReadByte(), + CodeViewLeafKind.Short => reader.ReadInt16(), + CodeViewLeafKind.UShort => reader.ReadUInt16(), + CodeViewLeafKind.Long => reader.ReadInt32(), + CodeViewLeafKind.ULong => reader.ReadUInt32(), + CodeViewLeafKind.QuadWord => reader.ReadInt64(), + CodeViewLeafKind.UQuadWord => reader.ReadUInt64(), + _ => 0 + }; + + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs new file mode 100644 index 000000000..b2d4a181c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +internal class SerializedFieldList : FieldList +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + public SerializedFieldList(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + reader.RelativeOffset = reader.Length; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList; + + protected override IList GetFields() + { + var reader = _reader.Fork(); + var result = new List(); + + while (reader.CanRead(sizeof(ushort))) + { + // Padding + while (true) + { + if ((CodeViewLeafKind) reader.ReadByte() < CodeViewLeafKind.Pad0) + { + reader.Offset--; + break; + } + } + + var leaf = FromReaderNoHeader(_context, 0, ref reader); + if (leaf is CodeViewField field) + result.Add(field); + else + _context.Parameters.ErrorListener.BadImage($"Field list contains a non-field leaf {leaf.LeafKind}."); + } + + return result; + } +} diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index d339ceab3..ececd387e 100644 --- a/src/AsmResolver/IO/BinaryStreamReader.cs +++ b/src/AsmResolver/IO/BinaryStreamReader.cs @@ -344,30 +344,36 @@ public byte[] ReadToEnd() /// public byte[] ReadBytesUntil(byte delimeter, bool includeDelimeterInReturn) { - bool shouldReadExtra = false; - var lookahead = Fork(); - while (lookahead.RelativeOffset < lookahead.Length) + bool hasConsumedDelimeter = lookahead.AdvanceUntil(delimeter, includeDelimeterInReturn); + + byte[] buffer = new byte[lookahead.RelativeOffset - RelativeOffset]; + ReadBytes(buffer, 0, buffer.Length); + + if (hasConsumedDelimeter) + ReadByte(); + + return buffer; + } + + public bool AdvanceUntil(byte delimeter, bool consumeDelimeter) + { + while (RelativeOffset < Length) { - byte b = lookahead.ReadByte(); + byte b = ReadByte(); if (b == delimeter) { - if (!includeDelimeterInReturn) + if (!consumeDelimeter) { - lookahead.RelativeOffset--; - shouldReadExtra = true; + RelativeOffset--; + return true; } - break; + + return false; } } - byte[] buffer = new byte[lookahead.RelativeOffset - RelativeOffset]; - ReadBytes(buffer, 0, buffer.Length); - - if (shouldReadExtra) - ReadByte(); - - return buffer; + return false; } /// From 1ef794ec51bb557cf2af073f01206d2a752a95d0 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 27 Jul 2022 22:31:45 +0200 Subject: [PATCH 056/182] BUGFIX: TpiStream::TryGetTypeRecordReader should return a reader that is sliced including the two header bytes. --- .../Metadata/Tpi/SerializedTpiStream.cs | 2 +- .../Leaves/EnumTypeTest.cs | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs index 4f11efe90..cf14cb700 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs @@ -75,7 +75,7 @@ public override bool TryGetTypeRecordReader(uint typeIndex, out BinaryStreamRead } (uint offset, uint length) = _recordOffsets[(int) typeIndex]; - reader = _recordsReader.ForkRelative(offset, length); + reader = _recordsReader.ForkRelative(offset, length + sizeof(ushort)); return true; } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs new file mode 100644 index 000000000..f4959a6bc --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs @@ -0,0 +1,37 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class EnumTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public EnumTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void FieldList() + { + var leaf = _fixture.SimplePdb.GetLeafRecord(0x1009); + var fields = Assert.IsAssignableFrom(leaf).Fields.Cast().ToArray(); + var names = new Utf8String[] + { + "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", + "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST", + "DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32", + }; + Assert.Equal(names[0], fields[0].Name); + Assert.Equal(names[1], fields[1].Name); + Assert.Equal(names[2], fields[2].Name); + Assert.Equal(names[3], fields[3].Name); + Assert.Equal(names[4], fields[4].Name); + Assert.Equal(names[5], fields[5].Name); + } +} From cecbf4acd6d1e0b58a8b3fd474bdfe0ea2343a78 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 27 Jul 2022 23:00:06 +0200 Subject: [PATCH 057/182] Rename EnumerateLeaf to EnumerateField. Add missing xmldoc. --- .../Leaves/CodeViewField.cs | 8 +- .../Leaves/CodeViewFieldAttributes.cs | 44 ++++++++++ .../Leaves/CodeViewLeaf.cs | 12 ++- .../Leaves/CodeViewType.cs | 8 +- .../Leaves/EnumType.cs | 47 ++++++---- .../Leaves/EnumerateField.cs | 86 +++++++++++++++++++ .../Leaves/EnumerateLeaf.cs | 56 ------------ .../Leaves/FieldList.cs | 26 +++++- .../Leaves/Serialized/SerializedEnumType.cs | 13 +-- ...ateLeaf.cs => SerializedEnumerateField.cs} | 13 ++- .../Leaves/Serialized/SerializedFieldList.cs | 17 +++- .../Metadata/Tpi/SerializedTpiStream.cs | 7 ++ .../Metadata/Tpi/TpiStream.cs | 79 +++++++++++++++++ .../Metadata/Tpi/TpiStreamVersion.cs | 5 ++ .../Records/ConstantSymbol.cs | 4 + src/AsmResolver/IO/BinaryStreamReader.cs | 8 ++ .../Leaves/EnumTypeTest.cs | 2 +- 17 files changed, 343 insertions(+), 92 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs delete mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/EnumerateLeaf.cs rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedEnumerateLeaf.cs => SerializedEnumerateField.cs} (63%) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs index 9148e9e5c..85651a502 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -1,8 +1,14 @@ namespace AsmResolver.Symbols.Pdb.Leaves; +/// +/// Represents a single record in a field list of a TPI or IPI stream. +/// public abstract class CodeViewField : CodeViewLeaf { - /// + /// + /// Initializes an empty CodeView field leaf. + /// + /// The type index to assign to the leaf. protected CodeViewField(uint typeIndex) : base(typeIndex) { diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs index 0c63f7e0b..789f51108 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs @@ -2,20 +2,64 @@ namespace AsmResolver.Symbols.Pdb.Leaves; +/// +/// Provides members defining all flags that can be assigned to a field, method or class. +/// [Flags] public enum CodeViewFieldAttributes : ushort { + /// + /// Indicates no attributes were assigned to the field. + /// None = 0b00000000_00000000, + + /// + /// Indicates the field is marked private. + /// Private = 0b00000000_00000001, + + /// + /// Indicates the field is marked protected. + /// Protected = 0b00000000_00000010, + + /// + /// Indicates the field is marked public. + /// Public = 0b00000000_00000011, + + /// + /// Provides the bit-mask that can be used to extract the access-level of the field. + /// AccessMask = 0b00000000_00000011, + /// + /// Provides the bit-mask that can be used to extract the method properties of the field. + /// MethodPropertiesMask = 0b00000000_00011100, + /// + /// Indicates the field is compiler generated and does not exist. + /// Pseudo = 0b00000000_00100000, + + /// + /// Indicates the class cannot be inherited. + /// NoInherit = 0b00000000_01000000, + + /// + /// Indicates the class cannot be constructed. + /// NoConstruct = 0b00000000_10000000, + + /// + /// Indicates the field is compiler generated but does exist. + /// CompilerGenerated = 0b00000001_00000000, + + /// + /// Indicates the method cannot be overridden. + /// Sealed = 0b00000010_00000000 } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 94692a7fa..01b2c1c7e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -4,10 +4,14 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// -/// Represents a single type record in a TPI or IPI stream. +/// Represents a single leaf record in a TPI or IPI stream. /// public abstract class CodeViewLeaf { + /// + /// Initializes an empty CodeView leaf. + /// + /// The type index to assign to the leaf. protected CodeViewLeaf(uint typeIndex) { TypeIndex = typeIndex; @@ -47,9 +51,9 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { - CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, ref dataReader), - CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, ref dataReader), - CodeViewLeafKind.Enumerate => new SerializedEnumerateLeaf(context, typeIndex, ref dataReader), + CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), + CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), + CodeViewLeafKind.Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), _ => new UnknownCodeViewType(kind, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs index 2ac9af947..1943e4b31 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs @@ -1,8 +1,14 @@ namespace AsmResolver.Symbols.Pdb.Leaves; +/// +/// Represents a single type record in a TPI or IPI stream. +/// public abstract class CodeViewType : CodeViewLeaf { - /// + /// + /// Initializes an empty CodeView type record. + /// + /// The type index to assign to the leaf. protected CodeViewType(uint typeIndex) : base(typeIndex) { diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs index 349994148..9992cb3f1 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs @@ -1,53 +1,70 @@ -using System.Collections.Generic; -using System.Threading; - namespace AsmResolver.Symbols.Pdb.Leaves; +/// +/// Represents an enum type. +/// public class EnumType : CodeViewType { private readonly LazyVariable _name; private readonly LazyVariable _type; - private IList? _fields; + private readonly LazyVariable _fields; + /// + /// Initializes a new empty enum type. + /// + /// The type index to assign to the enum type. protected EnumType(uint typeIndex) : base(typeIndex) { _name = new LazyVariable(GetName); _type = new LazyVariable(GetEnumUnderlyingType); + _fields = new LazyVariable(GetFields); } - public EnumType(Utf8String name, CodeViewLeaf underlyingType) + /// + /// Creates a new enum type. + /// + /// The name of the enum. + /// The underlying type of all members in the enum. + /// The structural attributes assigned to the enum. + public EnumType(Utf8String name, CodeViewLeaf underlyingType, StructureAttributes attributes) : base(0) { _name = new LazyVariable(name); _type = new LazyVariable(underlyingType); + _fields = new LazyVariable(new FieldList()); + StructureAttributes = attributes; } /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enum; + /// + /// Gets or sets the structural attributes assigned to the enum. + /// public StructureAttributes StructureAttributes { get; set; } + /// + /// Gets or sets the underlying type of all members in the enum. + /// public CodeViewLeaf EnumUnderlyingType { get => _type.Value; set => _type.Value = value; } - public IList Fields - { - get - { - if (_fields is null) - Interlocked.CompareExchange(ref _fields, GetFields(), null); - return _fields; - } - } + /// + /// Gets a collection of fields that are defined in the enum. + /// + public FieldList Fields => _fields.Value; + /// + /// Gets or sets the name of the enum type. + /// public Utf8String Name { get => _name.Value; @@ -79,7 +96,7 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual IList GetFields() => new List(); + protected virtual FieldList GetFields() => new(); /// public override string ToString() => Name; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs new file mode 100644 index 000000000..7ccbe7380 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs @@ -0,0 +1,86 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single enumerate field leaf in a field list. +/// +public class EnumerateField : CodeViewField +{ + private readonly LazyVariable _name; + private readonly LazyVariable _value; + + /// + /// Initializes an empty enumerate field leaf. + /// + /// The type index to assign to the enumerate field. + protected EnumerateField(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + _value = new LazyVariable(GetValue); + } + + /// + /// Creates a new enumerate field leaf. + /// + /// The name of the field. + /// The value assigned to the field. + /// The attributes associated to the field. + public EnumerateField(Utf8String name, object value, CodeViewFieldAttributes attributes) + : base(0) + { + _name = new LazyVariable(name); + _value = new LazyVariable(value); + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enumerate; + + /// + /// Gets or sets the name of the enumerate field. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Gets or sets the constant value assigned to the field. + /// + public object Value + { + get => _value.Value; + set => _value.Value = value; + } + + /// + /// Gets or sets the attributes associated to the field. + /// + public CodeViewFieldAttributes Attributes + { + get; + set; + } + + /// + /// Obtains the name of the field. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the value assigned to the field. + /// + /// The value. + /// + /// This method is called upon initialization of the property. + /// + protected virtual object? GetValue() => null; + + /// + public override string ToString() => $"{Name} = {Value}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateLeaf.cs deleted file mode 100644 index 9d99c8dba..000000000 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateLeaf.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace AsmResolver.Symbols.Pdb.Leaves; - -public class EnumerateLeaf : CodeViewField -{ - private readonly LazyVariable _name; - private readonly LazyVariable _value; - - protected EnumerateLeaf(uint typeIndex) - : base(typeIndex) - { - _name = new LazyVariable(GetName); - _value = new LazyVariable(GetValue); - } - - public EnumerateLeaf(Utf8String name, object value, CodeViewFieldAttributes attributes) - : base(0) - { - _name = new LazyVariable(name); - _value = new LazyVariable(value); - Attributes = attributes; - } - - /// - public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enumerate; - - /// - /// Gets or sets the name of the enumerate field. - /// - public Utf8String Name - { - get => _name.Value; - set => _name.Value = value; - } - - /// - /// Gets or sets the constant value assigned to the field. - /// - public object Value - { - get => _value.Value; - set => _value.Value = value; - } - - public CodeViewFieldAttributes Attributes - { - get; - set; - } - - protected virtual Utf8String GetName() => Utf8String.Empty; - - protected virtual object? GetValue() => null; - - /// - public override string ToString() => $"{Name} = {Value}"; -} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs index 4b080a9c6..72e596542 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs @@ -3,15 +3,25 @@ namespace AsmResolver.Symbols.Pdb.Leaves; +/// +/// Represents a leaf containing a list of fields. +/// public class FieldList : CodeViewLeaf { private IList? _fields; + /// + /// Initializes an empty field list. + /// + /// The type index to assign to the list. protected FieldList(uint typeIndex) : base(typeIndex) { } + /// + /// Creates a new empty field list. + /// public FieldList() : base(0) { @@ -20,15 +30,25 @@ public FieldList() /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList; - public IList Fields + /// + /// Gets a collection of fields stored in the list. + /// + public IList Entries { get { if (_fields is null) - Interlocked.CompareExchange(ref _fields, GetFields(), null); + Interlocked.CompareExchange(ref _fields, GetEntries(), null); return _fields; } } - protected virtual IList GetFields() => new List(); + /// + /// Obtains the fields stored in the list. + /// + /// The fields + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs index 5a61a1485..3c9a46331 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; using AsmResolver.IO; namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// public class SerializedEnumType : EnumType { private readonly PdbReaderContext _context; @@ -15,8 +17,9 @@ public class SerializedEnumType : EnumType /// Reads a constant symbol from the provided input stream. /// /// The reading context in which the symbol is situated in. + /// The type index to assign to the enum type. /// The input stream to read from. - public SerializedEnumType(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -40,15 +43,15 @@ public SerializedEnumType(PdbReaderContext context, uint typeIndex, ref BinarySt } /// - protected override IList GetFields() + protected override FieldList GetFields() { if (!_context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) || leaf is not SerializedFieldList list) { _context.Parameters.ErrorListener.BadImage( $"Enum type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); - return new List(); + return new FieldList(); } - return list.Fields; + return list; } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs similarity index 63% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateLeaf.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs index 14c2e162a..1c675fedf 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs @@ -2,11 +2,20 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; -public class SerializedEnumerateLeaf : EnumerateLeaf +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedEnumerateField : EnumerateField { private readonly BinaryStreamReader _nameReader; - public SerializedEnumerateLeaf(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + /// + /// Reads a enumerate field list from the provided input stream. + /// + /// The reading context in which the enumerate field is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedEnumerateField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) : base(typeIndex) { Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs index b2d4a181c..b2b558efb 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs @@ -3,13 +3,21 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// internal class SerializedFieldList : FieldList { private readonly PdbReaderContext _context; private readonly BinaryStreamReader _reader; - /// - public SerializedFieldList(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + /// + /// Reads a field list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedFieldList(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -20,14 +28,14 @@ public SerializedFieldList(PdbReaderContext context, uint typeIndex, ref BinaryS /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList; - protected override IList GetFields() + protected override IList GetEntries() { var reader = _reader.Fork(); var result = new List(); while (reader.CanRead(sizeof(ushort))) { - // Padding + // Skip padding bytes. while (true) { if ((CodeViewLeafKind) reader.ReadByte() < CodeViewLeafKind.Pad0) @@ -37,6 +45,7 @@ protected override IList GetFields() } } + // Read field. var leaf = FromReaderNoHeader(_context, 0, ref reader); if (leaf is CodeViewField field) result.Add(field); diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs index cf14cb700..16bdb06a0 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs @@ -6,11 +6,18 @@ namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// public class SerializedTpiStream : TpiStream { private readonly BinaryStreamReader _recordsReader; private List<(uint Offset, uint Length)>? _recordOffsets; + /// + /// Reads a TPI stream from the provided input stream. + /// + /// The input stream to read from. public SerializedTpiStream(BinaryStreamReader reader) { Version = (TpiStreamVersion) reader.ReadUInt32(); diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs index e9fe42889..5a4563d32 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs @@ -3,8 +3,14 @@ namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; +/// +/// Represents the Type Information (TPI) stream in a PDB file. +/// public abstract class TpiStream : SegmentBase { + /// + /// Gets the default fixed MSF stream index for the TPI stream. + /// public const int StreamIndex = 2; internal const uint TpiStreamHeaderSize = @@ -25,94 +31,163 @@ public abstract class TpiStream : SegmentBase + sizeof(uint) // HashAdjBufferLength ; + /// + /// Gets or sets the version of the file format that the TPI stream is using. + /// public TpiStreamVersion Version { get; set; } = TpiStreamVersion.V80; + /// + /// Gets or sets the index of the first type record in the stream. + /// public uint TypeIndexBegin { get; set; } = 0x1000; + /// + /// Gets or sets the index of the last type record in the stream. + /// public uint TypeIndexEnd { get; set; } + /// + /// Gets or sets the amount of bytes the full type record data requires. + /// public uint TypeRecordsByteCount { get; set; } + /// + /// Gets or sets the MSF stream index of the hash table for every record type in the stream (if available). + /// + /// + /// When this value is set to -1 (0xFFFF), then there is no hash stream stored in the PDB file. + /// public ushort HashStreamIndex { get; set; } + /// + /// Gets or sets the MSF stream index of the auxiliary hash table for every record type in the stream. + /// + /// + /// When this value is set to -1 (0xFFFF), then there is no hash stream stored in the PDB file. + /// The exact purpose of this stream is unknown, and usually this stream is not present. + /// public ushort HashAuxStreamIndex { get; set; } + /// + /// Gets or sets the number of bytes that a single hash value in the type hash stream consists of. + /// public uint HashKeySize { get; set; } + /// + /// Gets or sets the number of buckets used in the type record hash table. + /// public uint HashBucketCount { get; set; } + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of hash values. + /// public uint HashValueBufferOffset { get; set; } + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of hash values consists of. + /// public uint HashValueBufferLength { get; set; } + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of type record indices. + /// public uint IndexOffsetBufferOffset { get; set; } + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of type record indices consists of. + /// public uint IndexOffsetBufferLength { get; set; } + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of hash-index pairs. + /// public uint HashAdjBufferOffset { get; set; } + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of hash-index pairs consists of. + /// public uint HashAdjBufferLength { get; set; } + /// + /// Reads a TPI stream from the provided input stream. + /// + /// The input stream. + /// The TPI stream. public static TpiStream FromReader(BinaryStreamReader reader) => new SerializedTpiStream(reader); + /// + /// Attempts to get a reader object that starts at the beginning of a type record for the provided type index. + /// + /// The type index to get the reader for. + /// The obtained reader object. + /// + /// true if the provided type index was valid and a reader object was constructed successfully, + /// false otherwise. + /// public abstract bool TryGetTypeRecordReader(uint typeIndex, out BinaryStreamReader reader); + /// + /// Gets a reader object that starts at the beginning of a type record for the provided type index. + /// + /// The type index to get the reader for. + /// The obtained reader object. + /// Occurs when the provided type index was invalid. public BinaryStreamReader GetTypeRecordReader(uint typeIndex) { if (!TryGetTypeRecordReader(typeIndex, out var reader)) @@ -150,5 +225,9 @@ private void WriteHeader(IBinaryStreamWriter writer) writer.WriteUInt32(HashAdjBufferLength); } + /// + /// Writes all type records stored in the TPI stream to the provided output stream. + /// + /// The output stream. protected abstract void WriteTypeRecords(IBinaryStreamWriter writer); } diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs index dd20f4312..bf65e07ec 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs @@ -1,10 +1,15 @@ namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; +/// +/// Provides members defining all known file formats for the TPI stream. +/// public enum TpiStreamVersion : uint { +#pragma warning disable CS1591 V40 = 19950410, V41 = 19951122, V50 = 19961031, V70 = 19990903, V80 = 20040203, +#pragma warning restore CS1591 } diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs index aeafc45ee..795699f5e 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs @@ -2,6 +2,9 @@ namespace AsmResolver.Symbols.Pdb.Records; +/// +/// Represents a single constant symbol. +/// public class ConstantSymbol : CodeViewSymbol { private readonly LazyVariable _name; @@ -21,6 +24,7 @@ protected ConstantSymbol() /// /// The name of the type. /// The type. + /// The value to assign to the constant. public ConstantSymbol(Utf8String name, CodeViewType type, ushort value) { _name = new LazyVariable(name); diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index ececd387e..9de2fd993 100644 --- a/src/AsmResolver/IO/BinaryStreamReader.cs +++ b/src/AsmResolver/IO/BinaryStreamReader.cs @@ -356,6 +356,14 @@ public byte[] ReadBytesUntil(byte delimeter, bool includeDelimeterInReturn) return buffer; } + /// + /// Advances the reader until the provided delimeter byte is reached. + /// + /// The delimeter byte to stop at. + /// + /// true if the final delimeter should be consumed if available, false otherwise. + /// + /// true if the delimeter byte was found and consumed, false otherwise. public bool AdvanceUntil(byte delimeter, bool consumeDelimeter) { while (RelativeOffset < Length) diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs index f4959a6bc..60ce8a454 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs @@ -17,7 +17,7 @@ public EnumTypeTest(MockPdbFixture fixture) public void FieldList() { var leaf = _fixture.SimplePdb.GetLeafRecord(0x1009); - var fields = Assert.IsAssignableFrom(leaf).Fields.Cast().ToArray(); + var fields = Assert.IsAssignableFrom(leaf).Fields.Entries.Cast().ToArray(); var names = new Utf8String[] { "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", From f04ec640ea69fddf267904c7da30253b6365cb95 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 27 Jul 2022 23:58:04 +0200 Subject: [PATCH 058/182] Add read support LF_POINTER records. --- .../Leaves/CodeViewLeaf.cs | 1 + .../Leaves/EnumType.cs | 12 +- .../Leaves/PointerAttributes.cs | 145 +++++++ .../Leaves/PointerType.cs | 356 ++++++++++++++++++ .../Leaves/Serialized/SerializedEnumType.cs | 6 +- .../Serialized/SerializedPointerType.cs | 38 ++ 6 files changed, 549 insertions(+), 9 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/PointerType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerType.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 01b2c1c7e..32dd6b285 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -54,6 +54,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), CodeViewLeafKind.Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), + CodeViewLeafKind.Pointer => new SerializedPointerType(context, typeIndex, dataReader), _ => new UnknownCodeViewType(kind, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs index 9992cb3f1..e1c33ead5 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs @@ -6,7 +6,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; public class EnumType : CodeViewType { private readonly LazyVariable _name; - private readonly LazyVariable _type; + private readonly LazyVariable _type; private readonly LazyVariable _fields; /// @@ -17,7 +17,7 @@ protected EnumType(uint typeIndex) : base(typeIndex) { _name = new LazyVariable(GetName); - _type = new LazyVariable(GetEnumUnderlyingType); + _type = new LazyVariable(GetEnumUnderlyingType); _fields = new LazyVariable(GetFields); } @@ -27,11 +27,11 @@ protected EnumType(uint typeIndex) /// The name of the enum. /// The underlying type of all members in the enum. /// The structural attributes assigned to the enum. - public EnumType(Utf8String name, CodeViewLeaf underlyingType, StructureAttributes attributes) + public EnumType(Utf8String name, CodeViewType underlyingType, StructureAttributes attributes) : base(0) { _name = new LazyVariable(name); - _type = new LazyVariable(underlyingType); + _type = new LazyVariable(underlyingType); _fields = new LazyVariable(new FieldList()); StructureAttributes = attributes; } @@ -51,7 +51,7 @@ public StructureAttributes StructureAttributes /// /// Gets or sets the underlying type of all members in the enum. /// - public CodeViewLeaf EnumUnderlyingType + public CodeViewType EnumUnderlyingType { get => _type.Value; set => _type.Value = value; @@ -87,7 +87,7 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewLeaf? GetEnumUnderlyingType() => null; + protected virtual CodeViewType? GetEnumUnderlyingType() => null; /// /// Obtains the fields defined in the enum type. diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs new file mode 100644 index 000000000..a1c9ac743 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs @@ -0,0 +1,145 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible flags that can be assigned to a pointer type in a TPI or IPI stream. +/// +[Flags] +public enum PointerAttributes : uint +{ + /// + /// Indicates the pointer is a 16 bit pointer. + /// + Near16 = 0b0000000000_000_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 16:16 far pointer. + /// + Far16 = 0b0000000000_000_000000_00000_000_000001, + + /// + /// Indicates the pointer is a 16:16 huge pointer. + /// + Huge16 = 0b0000000000_000_000000_00000_000_000010, + + /// + /// Indicates the pointer is a based on segment. + /// + BasedOnSegment = 0b0000000000_000_000000_00000_000_000011, + + /// + /// Indicates the pointer is a based on value of base. + /// + BasedOnValue = 0b0000000000_000_000000_00000_000_000100, + + /// + /// Indicates the pointer is a based on segment value of base. + /// + BasedOnSegmentValue = 0b0000000000_000_000000_00000_000_000101, + + /// + /// Indicates the pointer is a based on address of base. + /// + BasedOnAddress = 0b0000000000_000_000000_00000_000_000110, + + /// + /// Indicates the pointer is a based on segment address of base. + /// + BasedOnSegmentAddress = 0b0000000000_000_000000_00000_000_000111, + + /// + /// Indicates the pointer is a based on type. + /// + BasedOnType = 0b0000000000_000_000000_00000_000_001000, + + /// + /// Indicates the pointer is a based on self. + /// + BasedOnSelf = 0b0000000000_000_000000_00000_000_001001, + + /// + /// Indicates the pointer is a 32 bit pointer. + /// + Near32 = 0b0000000000_000_000000_00000_000_001010, + + /// + /// Indicates the pointer is a 16:32 pointer. + /// + Far32 = 0b0000000000_000_000000_00000_000_001011, + + /// + /// Indicates the pointer is a 64 bit pointer. + /// + Near64 = 0b0000000000_000_000000_00000_000_001100, + + /// + /// Provides the bit-mask for extracting the pointer kind from the flags. + /// + KindMask = 0b0000000000_000_000000_00000_000_11111, + + /// + /// Indicates the pointer is an "old" reference. + /// + LValueReference = 0b0000000000_000_000000_00000_001_00000, + + /// + /// Indicates the pointer is a pointer to data member. + /// + PointerToDataMember = 0b0000000000_000_000000_00000_010_00000, + + /// + /// Indicates the pointer is a pointer to member function. + /// + PointerToMemberFunction = 0b0000000000_000_000000_00000_011_00000, + + /// + /// Indicates the pointer is an r-value reference. + /// + RValueReference = 0b0000000000_000_000000_00000_100_00000, + + /// + /// Provides the bit-mask for extracting the pointer mode from the flags. + /// + ModeMask = 0b0000000000_000_000000_00000_111_00000, + + /// + /// Indicates the pointer is a "flat" pointer. + /// + Flat32 = 0b0000000000_000_000000_00001_000_00000, + + /// + /// Indicates the pointer is marked volatile. + /// + Volatile = 0b0000000000_000_000000_00010_000_00000, + + /// + /// Indicates the pointer is marked const. + /// + Const = 0b0000000000_000_000000_00100_000_00000, + + /// + /// Indicates the pointer is marked unaligned. + /// + Unaligned = 0b0000000000_000_000000_01000_000_00000, + + /// + /// Indicates the pointer is marked restrict. + /// + Restrict = 0b0000000000_000_000000_10000_000_00000, + + /// + /// Indicates the pointer is a WinRT smart pointer. + /// + WinRTSmartPointer = 0b0000000000_001_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 'this' pointer of a member function with ref qualifier. + /// + LValueRefThisPointer = 0b0000000000_010_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 'this' pointer of a member function with ref qualifier. + /// + RValueRefThisPointer = 0b0000000000_100_000000_00000_000_00000 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerType.cs new file mode 100644 index 000000000..5bda18af4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerType.cs @@ -0,0 +1,356 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a pointer type in a TPI or IPI stream. +/// +public class PointerType : CodeViewType +{ + private readonly LazyVariable _baseType; + + /// + /// Initializes a new empty pointer type. + /// + /// The type index to assign to the type. + protected PointerType(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new pointer type. + /// + /// The referent type. + /// The attributes describing the shape of the pointer. + public PointerType(CodeViewType type, PointerAttributes attributes) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + } + + /// + /// Creates a new pointer type. + /// + /// The referent type. + /// The attributes describing the shape of the pointer. + /// The size of the pointer. + public PointerType(CodeViewType type, PointerAttributes attributes, byte size) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + Size = size; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Pointer; + + /// + /// Gets or sets the referent type of the pointer. + /// + public CodeViewType BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the attributes describing the shape of the pointer type. + /// + public PointerAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the kind of the pointer. + /// + public PointerAttributes Kind + { + get => Attributes & PointerAttributes.KindMask; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) | (value & PointerAttributes.KindMask); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16 bit pointer. + /// + public bool IsNear16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:16 far pointer. + /// + public bool IsFar16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Far16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Far16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:16 huge pointer. + /// + public bool IsHuge16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Huge16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Huge16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment. + /// + public bool IsBasedOnSegment + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegment; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegment : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on value of base. + /// + public bool IsBasedOnValue + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnValue; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnValue : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment value of base. + /// + public bool IsBasedOnSegmentValue + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegmentValue; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegmentValue : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on address of base. + /// + public bool IsBasedOnAddress + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnAddress; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnAddress : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment address of base. + /// + public bool IsBasedOnSegmentAddress + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegmentAddress; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegmentAddress : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on type. + /// + public bool IsBasedOnType + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnType; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnType : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on self. + /// + public bool IsBasedOnSelf + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSelf; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSelf : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 32 bit pointer. + /// + public bool IsNear32 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near32; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:32 pointer. + /// + public bool IsFar32 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Far32; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Far32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 64 bit pointer. + /// + public bool IsNear64 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near64; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near64 : 0); + } + + /// + /// Gets or sets the mode of the pointer. + /// + public PointerAttributes Mode + { + get => Attributes & PointerAttributes.ModeMask; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) | (value & PointerAttributes.ModeMask); + } + + /// + /// Gets or sets a value indicating whether the pointer is an "old" reference. + /// + public bool IsLValueReference + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.LValueReference; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.LValueReference : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a pointer to data member. + /// + public bool IsPointerToDataMember + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.PointerToDataMember; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.PointerToDataMember : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a pointer to member function. + /// + public bool IsPointerToMemberFunction + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.PointerToMemberFunction; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.PointerToMemberFunction : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is an r-value reference. + /// + public bool IsRValueReference + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.RValueReference; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.RValueReference : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a "flat" pointer. + /// + public bool IsFlat32 + { + get => (Attributes & PointerAttributes.Flat32) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Flat32) + | (value ? PointerAttributes.Flat32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked volatile. + /// + public bool IsVolatile + { + get => (Attributes & PointerAttributes.Volatile) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Volatile) + | (value ? PointerAttributes.Volatile : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked const. + /// + public bool IsConst + { + get => (Attributes & PointerAttributes.Const) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Const) + | (value ? PointerAttributes.Const : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked unaligned. + /// + public bool IsUnaligned + { + get => (Attributes & PointerAttributes.Unaligned) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Unaligned) + | (value ? PointerAttributes.Unaligned : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked restrict. + /// + public bool IsRestrict + { + get => (Attributes & PointerAttributes.Restrict) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Restrict) + | (value ? PointerAttributes.Restrict : 0); + } + + /// + /// Gets or sets the size of the pointer. + /// + public byte Size + { + get => (byte) (((uint) Attributes >> 0xD) & 0b111111); + set => Attributes = (PointerAttributes) (((uint) Attributes & ~(0b111111u << 0xD)) + | (((uint) value & 0b111111) << 0xD)); + } + + /// + /// Gets or sets a value indicating whether the pointer is a WinRT smart pointer. + /// + public bool IsWinRTSmartPointer + { + get => (Attributes & PointerAttributes.WinRTSmartPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.WinRTSmartPointer) + | (value ? PointerAttributes.WinRTSmartPointer : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 'this' pointer of a member function with ref qualifier. + /// + public bool IsLValueRefThisPointer + { + get => (Attributes & PointerAttributes.LValueRefThisPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.LValueRefThisPointer) + | (value ? PointerAttributes.LValueRefThisPointer : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 'this' pointer of a member function with ref qualifier. + /// + public bool IsRValueRefThisPointer + { + get => (Attributes & PointerAttributes.RValueRefThisPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.RValueRefThisPointer) + | (value ? PointerAttributes.RValueRefThisPointer : 0); + } + + /// + /// Obtains the base type of the pointer. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetBaseType() => null; + + /// + public override string ToString() => $"({BaseType})*"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs index 3c9a46331..679e58c91 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs @@ -34,11 +34,11 @@ public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStream protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewLeaf? GetEnumUnderlyingType() + protected override CodeViewType? GetEnumUnderlyingType() { - return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var type) + return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var leaf) && leaf is CodeViewType type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}."); } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerType.cs new file mode 100644 index 000000000..b1098cc33 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerType.cs @@ -0,0 +1,38 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedPointerType : PointerType +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a pointer type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedPointerType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Attributes = (PointerAttributes) reader.ReadUInt32(); + + // TODO: member pointer info + } + + /// + protected override CodeViewType? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Pointer {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + + } +} From f168740360106458245b9469d61568054a68c8df Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 00:07:02 +0200 Subject: [PATCH 059/182] Add read support for LF_MODIFIER records. --- .../Leaves/CodeViewLeaf.cs | 3 +- .../Leaves/ModifierAttributes.cs | 26 ++++++++ .../Leaves/ModifierType.cs | 64 +++++++++++++++++++ .../Serialized/SerializedModifierType.cs | 35 ++++++++++ .../Records/UserDefinedTypeTest.cs | 7 ++ 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierType.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 32dd6b285..806d6490c 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -51,9 +51,10 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { - CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), CodeViewLeafKind.Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), + CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), + CodeViewLeafKind.Modifier => new SerializedModifierType(context, typeIndex, dataReader), CodeViewLeafKind.Pointer => new SerializedPointerType(context, typeIndex, dataReader), _ => new UnknownCodeViewType(kind, dataReader.ReadToEnd()) }; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs new file mode 100644 index 000000000..3c221e900 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs @@ -0,0 +1,26 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible modifiers that can be added to a type using a Modifier type record in a +/// TPI or IPI stream. +/// +[Flags] +public enum ModifierAttributes : ushort +{ + /// + /// Indicates the type is marked as const. + /// + Const = 1, + + /// + /// Indicates the type is marked as volatile. + /// + Volatile = 2, + + /// + /// Indicates the type is marked as unaligned. + /// + Unaligned = 4, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs new file mode 100644 index 000000000..4b25ad895 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs @@ -0,0 +1,64 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type that is annotated with extra modifiers. +/// +public class ModifierType : CodeViewType +{ + private readonly LazyVariable _baseType; + + /// + /// Initializes a new empty modifier type. + /// + /// The type index to assign to the modifier type. + protected ModifierType(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new modified type. + /// + /// The type to be modified. + /// The attributes describing the shape of the pointer. + public ModifierType(CodeViewType type, ModifierAttributes attributes) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Modifier; + + /// + /// Gets or sets the type that is annotated. + /// + public CodeViewType BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the annotations that were added to the type. + /// + public ModifierAttributes Attributes + { + get; + set; + } + + /// + /// Obtains the base type of the modifier type. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetBaseType() => null; + + /// + public override string ToString() => $"{BaseType} {Attributes}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierType.cs new file mode 100644 index 000000000..882263042 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierType.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedModifierType : ModifierType +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a pointer type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedModifierType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Attributes = (ModifierAttributes) reader.ReadUInt32(); + } + + /// + protected override CodeViewType? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Modifier type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs index a25234fb1..5aa653aeb 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs @@ -28,4 +28,11 @@ public void Type() var type = Assert.IsAssignableFrom(udt.Type); Assert.Equal(SimpleTypeKind.UInt32, type.Kind); } + + [Fact] + public void Type2() + { + var udt = _fixture.SimplePdb.Symbols.OfType().ElementAt(1); + Assert.Equal(CodeViewLeafKind.UdtSrcLine, udt.Type.LeafKind); + } } From ca8436e6b459edc2b166065586cb7028afa0f7f0 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 00:11:55 +0200 Subject: [PATCH 060/182] Rename UnknownCodeViewType to UnknownCodeViewLeaf --- src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs | 2 +- .../{UnknownCodeViewType.cs => UnknownCodeViewLeaf.cs} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/AsmResolver.Symbols.Pdb/Leaves/{UnknownCodeViewType.cs => UnknownCodeViewLeaf.cs} (86%) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 806d6490c..911bc17d5 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -56,7 +56,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), CodeViewLeafKind.Modifier => new SerializedModifierType(context, typeIndex, dataReader), CodeViewLeafKind.Pointer => new SerializedPointerType(context, typeIndex, dataReader), - _ => new UnknownCodeViewType(kind, dataReader.ReadToEnd()) + _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) }; } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs similarity index 86% rename from src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs index 7426b39b4..7fd7898f1 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs @@ -3,14 +3,14 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents an unknown or unsupported CodeView type record. /// -public class UnknownCodeViewType : CodeViewLeaf +public class UnknownCodeViewLeaf : CodeViewLeaf { /// /// Creates a new unknown type record. /// /// The type of symbol. /// The raw data stored in the record. - public UnknownCodeViewType(CodeViewLeafKind leafKind, byte[] data) + public UnknownCodeViewLeaf(CodeViewLeafKind leafKind, byte[] data) : this(0, leafKind, data) { } @@ -21,7 +21,7 @@ public UnknownCodeViewType(CodeViewLeafKind leafKind, byte[] data) /// The type index to assign to the type /// The type of symbol. /// The raw data stored in the record. - internal UnknownCodeViewType(uint typeIndex, CodeViewLeafKind leafKind, byte[] data) + internal UnknownCodeViewLeaf(uint typeIndex, CodeViewLeafKind leafKind, byte[] data) : base(typeIndex) { LeafKind = leafKind; From 53f3e8bc6bf547b730be86b1abf2e11b59fcb8cf Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 00:16:54 +0200 Subject: [PATCH 061/182] Add convenience properties to ModifierType. --- .../Leaves/ModifierType.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs index 4b25ad895..6ac452c4a 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs @@ -50,6 +50,36 @@ public ModifierAttributes Attributes set; } + /// + /// Gets or sets a value indicating whether the type is marked as const. + /// + public bool IsConst + { + get => (Attributes & ModifierAttributes.Const) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Const) + | (value ? ModifierAttributes.Const : 0); + } + + /// + /// Gets or sets a value indicating whether the type is marked as volatile. + /// + public bool IsVolatile + { + get => (Attributes & ModifierAttributes.Volatile) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Volatile) + | (value ? ModifierAttributes.Volatile : 0); + } + + /// + /// Gets or sets a value indicating whether the type is marked as unaligned. + /// + public bool IsUnaligned + { + get => (Attributes & ModifierAttributes.Unaligned) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Unaligned) + | (value ? ModifierAttributes.Unaligned : 0); + } + /// /// Obtains the base type of the modifier type. /// From 1e9cc01b27b01d18ce144c1e4d0ee9c1d5c0500f Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 00:54:16 +0200 Subject: [PATCH 062/182] Extract CodeViewComplexType from EnumType, add read support for LF_CLASS, LF_STRUCTURE and LF_INTERFACE records. --- .../Leaves/ClassType.cs | 89 +++++++++++++++++++ .../Leaves/CodeViewComplexType.cs | 86 ++++++++++++++++++ .../Leaves/CodeViewLeaf.cs | 20 +++++ .../Leaves/EnumType.cs | 76 +--------------- .../Leaves/Serialized/SerializedClassType.cs | 75 ++++++++++++++++ .../Leaves/Serialized/SerializedEnumType.cs | 7 +- .../Serialized/SerializedEnumerateField.cs | 14 +-- src/AsmResolver.Symbols.Pdb/PdbImage.cs | 2 +- .../Records/UserDefinedTypeTest.cs | 2 +- 9 files changed, 281 insertions(+), 90 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/CodeViewComplexType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs new file mode 100644 index 000000000..849ff9327 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs @@ -0,0 +1,89 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a class, structure or interface type in a PDB. +/// +public class ClassType : CodeViewComplexType +{ + private readonly LazyVariable _uniqueName; + + /// + /// Initializes an empty class type. + /// + /// The kind of type. + /// The type index to assign to the class type. + /// + /// Occurs when the provided kind is not a class, structure or interface. + /// + protected ClassType(CodeViewLeafKind kind, uint typeIndex) + : base(typeIndex) + { + if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface)) + throw new ArgumentOutOfRangeException(nameof(kind)); + + LeafKind = kind; + _uniqueName = new LazyVariable(GetUniqueName); + } + + /// + /// Creates a new class type record. + /// + /// The kind. + /// The name of the type. + /// The unique mangled name of the type. + /// The size in bytes of the type. + /// Attributes describing the shape of the type. + /// The type that this type is derived from, if any. + /// + /// Occurs when the provided kind is not a class, structure or interface. + /// + public ClassType(CodeViewLeafKind kind, Utf8String name, Utf8String uniqueName, ulong size, + StructureAttributes attributes, CodeViewType? baseType) + : base(0) + { + if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface)) + throw new ArgumentOutOfRangeException(nameof(kind)); + + LeafKind = kind; + Name = name; + _uniqueName = new LazyVariable(uniqueName); + Size = size; + StructureAttributes = attributes; + BaseType = baseType; + } + + /// + public override CodeViewLeafKind LeafKind + { + get; + } + + /// + /// Gets or sets the number bytes that this class spans. + /// + public ulong Size + { + get; + set; + } + + /// + /// Gets or sets the uniquely identifiable name for this type. + /// + public Utf8String UniqueName + { + get => _uniqueName.Value; + set => _uniqueName.Value = value; + } + + /// + /// Obtains the uniquely identifiable name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetUniqueName() => Utf8String.Empty; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewComplexType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewComplexType.cs new file mode 100644 index 000000000..13ce72f52 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewComplexType.cs @@ -0,0 +1,86 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides a base for all custom types that may define fields and/or are derived from a base type. +/// +public abstract class CodeViewComplexType : CodeViewType +{ + private readonly LazyVariable _name; + private readonly LazyVariable _baseType; + private readonly LazyVariable _fields; + + /// + protected CodeViewComplexType(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + _baseType = new LazyVariable(GetBaseType); + _fields = new LazyVariable(GetFields); + } + + /// + /// Gets or sets the name of the enum type. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Gets or sets the structural attributes assigned to the type. + /// + public StructureAttributes StructureAttributes + { + get; + set; + } + + /// + /// Gets or sets the base type that this type is deriving from. + /// + public CodeViewType? BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets a collection of fields that are defined in the enum. + /// + public FieldList? Fields + { + get => _fields.Value; + set => _fields.Value = value; + } + + /// + /// Obtains the new name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the type that the type is derived from. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetBaseType() => null; + + /// + /// Obtains the fields defined in the type. + /// + /// The fields. + /// + /// This method is called upon initialization of the property. + /// + protected virtual FieldList? GetFields() => null; + + /// + public override string ToString() => Name; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 911bc17d5..95918f7f3 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -51,12 +51,32 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { + CodeViewLeafKind.Class => new SerializedClassType(CodeViewLeafKind.Class, context, typeIndex, dataReader), CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), CodeViewLeafKind.Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), + CodeViewLeafKind.Interface => new SerializedClassType(CodeViewLeafKind.Interface, context, typeIndex, dataReader), CodeViewLeafKind.Modifier => new SerializedModifierType(context, typeIndex, dataReader), CodeViewLeafKind.Pointer => new SerializedPointerType(context, typeIndex, dataReader), + CodeViewLeafKind.Structure => new SerializedClassType(CodeViewLeafKind.Structure, context, typeIndex, dataReader), _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) }; } + + internal static object ReadNumeric(ref BinaryStreamReader reader) + { + var kind = (CodeViewLeafKind) reader.ReadUInt16(); + return kind switch + { + < CodeViewLeafKind.Numeric => (object) (uint) kind, + CodeViewLeafKind.Char => (char) reader.ReadByte(), + CodeViewLeafKind.Short => reader.ReadInt16(), + CodeViewLeafKind.UShort => reader.ReadUInt16(), + CodeViewLeafKind.Long => reader.ReadInt32(), + CodeViewLeafKind.ULong => reader.ReadUInt32(), + CodeViewLeafKind.QuadWord => reader.ReadInt64(), + CodeViewLeafKind.UQuadWord => reader.ReadUInt64(), + _ => 0 + }; + } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs index e1c33ead5..1c9e9e0b8 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs @@ -3,12 +3,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents an enum type. /// -public class EnumType : CodeViewType +public class EnumType : CodeViewComplexType { - private readonly LazyVariable _name; - private readonly LazyVariable _type; - private readonly LazyVariable _fields; - /// /// Initializes a new empty enum type. /// @@ -16,9 +12,6 @@ public class EnumType : CodeViewType protected EnumType(uint typeIndex) : base(typeIndex) { - _name = new LazyVariable(GetName); - _type = new LazyVariable(GetEnumUnderlyingType); - _fields = new LazyVariable(GetFields); } /// @@ -30,74 +23,11 @@ protected EnumType(uint typeIndex) public EnumType(Utf8String name, CodeViewType underlyingType, StructureAttributes attributes) : base(0) { - _name = new LazyVariable(name); - _type = new LazyVariable(underlyingType); - _fields = new LazyVariable(new FieldList()); + Name = name; + BaseType = underlyingType; StructureAttributes = attributes; } /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enum; - - /// - /// Gets or sets the structural attributes assigned to the enum. - /// - public StructureAttributes StructureAttributes - { - get; - set; - } - - /// - /// Gets or sets the underlying type of all members in the enum. - /// - public CodeViewType EnumUnderlyingType - { - get => _type.Value; - set => _type.Value = value; - } - - /// - /// Gets a collection of fields that are defined in the enum. - /// - public FieldList Fields => _fields.Value; - - /// - /// Gets or sets the name of the enum type. - /// - public Utf8String Name - { - get => _name.Value; - set => _name.Value = value; - } - - /// - /// Obtains the new name of the enum type. - /// - /// The name. - /// - /// This method is called upon initialization of the property. - /// - protected virtual Utf8String GetName() => Utf8String.Empty; - - /// - /// Obtains the type that the enum is based on. - /// - /// The type. - /// - /// This method is called upon initialization of the property. - /// - protected virtual CodeViewType? GetEnumUnderlyingType() => null; - - /// - /// Obtains the fields defined in the enum type. - /// - /// The fields. - /// - /// This method is called upon initialization of the property. - /// - protected virtual FieldList GetFields() => new(); - - /// - public override string ToString() => Name; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs new file mode 100644 index 000000000..720ed0972 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs @@ -0,0 +1,75 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedClassType : ClassType +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _baseTypeIndex; + private readonly uint _fieldIndex; + private readonly uint _vshapeIndex; + private readonly BinaryStreamReader _nameReader; + private readonly BinaryStreamReader _uniqueNameReader; + + /// + /// Reads a class type from the provided input stream. + /// + /// The kind of type that is being read. + /// The reading context in which the type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedClassType(CodeViewLeafKind kind, PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(kind, typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _fieldIndex = reader.ReadUInt32(); + _baseTypeIndex = reader.ReadUInt32(); + _vshapeIndex = reader.ReadUInt32(); + + Size = (uint) ReadNumeric(ref reader); + + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + _uniqueNameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewType? GetBaseType() + { + if (_baseTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid underlying enum type index {_baseTypeIndex:X8}."); + } + + /// + protected override FieldList? GetFields() + { + if (_fieldIndex == 0) + return null; + + if (!_context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) || leaf is not SerializedFieldList list) + { + _context.Parameters.ErrorListener.BadImage( + $"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + return new FieldList(); + } + + return list; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs index 679e58c91..542153c63 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs @@ -34,7 +34,7 @@ public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStream protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetEnumUnderlyingType() + protected override CodeViewType? GetBaseType() { return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var leaf) && leaf is CodeViewType type ? type @@ -43,8 +43,11 @@ public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStream } /// - protected override FieldList GetFields() + protected override FieldList? GetFields() { + if (_fieldIndex == 0) + return null; + if (!_context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) || leaf is not SerializedFieldList list) { _context.Parameters.ErrorListener.BadImage( diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs index 1c675fedf..91457f27f 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs @@ -19,21 +19,9 @@ public SerializedEnumerateField(PdbReaderContext context, uint typeIndex, ref Bi : base(typeIndex) { Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); - var kind = (CodeViewLeafKind) reader.ReadUInt16(); // We need to eagerly initialize the value because it is the only way to know how large the leaf is. - Value = kind switch - { - < CodeViewLeafKind.Numeric => (object) (uint) kind, - CodeViewLeafKind.Char => (char) reader.ReadByte(), - CodeViewLeafKind.Short => reader.ReadInt16(), - CodeViewLeafKind.UShort => reader.ReadUInt16(), - CodeViewLeafKind.Long => reader.ReadInt32(), - CodeViewLeafKind.ULong => reader.ReadUInt32(), - CodeViewLeafKind.QuadWord => reader.ReadInt64(), - CodeViewLeafKind.UQuadWord => reader.ReadUInt64(), - _ => 0 - }; + Value = ReadNumeric(ref reader); _nameReader = reader; reader.AdvanceUntil(0, true); diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index 10cd6eaea..7ffde2e25 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -89,7 +89,7 @@ public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParamete public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? type) { typeIndex &= 0x7fffffff; - if (typeIndex < 0x1000) + if (typeIndex is > 0 and < 0x1000) { type = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleType(i)); return true; diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs index 5aa653aeb..5b7365220 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs @@ -33,6 +33,6 @@ public void Type() public void Type2() { var udt = _fixture.SimplePdb.Symbols.OfType().ElementAt(1); - Assert.Equal(CodeViewLeafKind.UdtSrcLine, udt.Type.LeafKind); + Assert.Equal(CodeViewLeafKind.Pointer, udt.Type.LeafKind); } } From 6bd00eddaca27ec5ffeb9291b44e9c29231789c8 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 10:33:02 +0200 Subject: [PATCH 063/182] Add read support LF_VTSHAPE records. --- .../Leaves/ClassType.cs | 21 +++++++ .../Leaves/CodeViewLeaf.cs | 42 ++++++------- .../Leaves/Serialized/SerializedClassType.cs | 19 ++++-- .../Serialized/SerializedVTableShape.cs | 50 +++++++++++++++ .../Leaves/VTableShape.cs | 63 +++++++++++++++++++ .../Leaves/VTableShapeEntry.cs | 17 +++++ 6 files changed, 186 insertions(+), 26 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/VTableShape.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs index 849ff9327..fb1f8c2ed 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs @@ -8,6 +8,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; public class ClassType : CodeViewComplexType { private readonly LazyVariable _uniqueName; + private readonly LazyVariable _vtableShape; /// /// Initializes an empty class type. @@ -25,6 +26,7 @@ protected ClassType(CodeViewLeafKind kind, uint typeIndex) LeafKind = kind; _uniqueName = new LazyVariable(GetUniqueName); + _vtableShape = new LazyVariable(GetVTableShape); } /// @@ -49,6 +51,7 @@ protected ClassType(CodeViewLeafKind kind, uint typeIndex) LeafKind = kind; Name = name; _uniqueName = new LazyVariable(uniqueName); + _vtableShape = new LazyVariable(default(VTableShape)); Size = size; StructureAttributes = attributes; BaseType = baseType; @@ -78,6 +81,15 @@ public Utf8String UniqueName set => _uniqueName.Value = value; } + /// + /// Gets or sets the shape of the virtual function table of this type, if available. + /// + public VTableShape? VTableShape + { + get => _vtableShape.Value; + set => _vtableShape.Value = value; + } + /// /// Obtains the uniquely identifiable name of the type. /// @@ -86,4 +98,13 @@ public Utf8String UniqueName /// This method is called upon initialization of the property. /// protected virtual Utf8String GetUniqueName() => Utf8String.Empty; + + /// + /// Obtains the shape of the virtual function table name of the type. + /// + /// The shape. + /// + /// This method is called upon initialization of the property. + /// + protected virtual VTableShape? GetVTableShape() => null; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 95918f7f3..d7c22a20d 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -1,5 +1,6 @@ using AsmResolver.IO; using AsmResolver.Symbols.Pdb.Leaves.Serialized; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewLeafKind; namespace AsmResolver.Symbols.Pdb.Leaves; @@ -51,32 +52,29 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { - CodeViewLeafKind.Class => new SerializedClassType(CodeViewLeafKind.Class, context, typeIndex, dataReader), - CodeViewLeafKind.Enum => new SerializedEnumType(context, typeIndex, dataReader), - CodeViewLeafKind.Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), + Class => new SerializedClassType(Class, context, typeIndex, dataReader), + Enum => new SerializedEnumType(context, typeIndex, dataReader), + Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), - CodeViewLeafKind.Interface => new SerializedClassType(CodeViewLeafKind.Interface, context, typeIndex, dataReader), - CodeViewLeafKind.Modifier => new SerializedModifierType(context, typeIndex, dataReader), - CodeViewLeafKind.Pointer => new SerializedPointerType(context, typeIndex, dataReader), - CodeViewLeafKind.Structure => new SerializedClassType(CodeViewLeafKind.Structure, context, typeIndex, dataReader), + Interface => new SerializedClassType(Interface, context, typeIndex, dataReader), + Modifier => new SerializedModifierType(context, typeIndex, dataReader), + Pointer => new SerializedPointerType(context, typeIndex, dataReader), + Structure => new SerializedClassType(Structure, context, typeIndex, dataReader), + VTShape => new SerializedVTableShape(context, typeIndex, dataReader), _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) }; } - internal static object ReadNumeric(ref BinaryStreamReader reader) + internal static object ReadNumeric(ref BinaryStreamReader reader) => (CodeViewLeafKind) reader.ReadUInt16() switch { - var kind = (CodeViewLeafKind) reader.ReadUInt16(); - return kind switch - { - < CodeViewLeafKind.Numeric => (object) (uint) kind, - CodeViewLeafKind.Char => (char) reader.ReadByte(), - CodeViewLeafKind.Short => reader.ReadInt16(), - CodeViewLeafKind.UShort => reader.ReadUInt16(), - CodeViewLeafKind.Long => reader.ReadInt32(), - CodeViewLeafKind.ULong => reader.ReadUInt32(), - CodeViewLeafKind.QuadWord => reader.ReadInt64(), - CodeViewLeafKind.UQuadWord => reader.ReadUInt64(), - _ => 0 - }; - } + < Numeric => (object) (uint) (CodeViewLeafKind) reader.ReadUInt16(), + Char => (char) reader.ReadByte(), + Short => reader.ReadInt16(), + UShort => reader.ReadUInt16(), + Long => reader.ReadInt32(), + ULong => reader.ReadUInt32(), + QuadWord => reader.ReadInt64(), + UQuadWord => reader.ReadUInt64(), + _ => 0 + }; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs index 720ed0972..4fe10e895 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs @@ -11,7 +11,7 @@ public class SerializedClassType : ClassType private readonly ushort _memberCount; private readonly uint _baseTypeIndex; private readonly uint _fieldIndex; - private readonly uint _vshapeIndex; + private readonly uint _vTableShapeIndex; private readonly BinaryStreamReader _nameReader; private readonly BinaryStreamReader _uniqueNameReader; @@ -30,7 +30,7 @@ public SerializedClassType(CodeViewLeafKind kind, PdbReaderContext context, uint StructureAttributes = (StructureAttributes) reader.ReadUInt16(); _fieldIndex = reader.ReadUInt32(); _baseTypeIndex = reader.ReadUInt32(); - _vshapeIndex = reader.ReadUInt32(); + _vTableShapeIndex = reader.ReadUInt32(); Size = (uint) ReadNumeric(ref reader); @@ -65,11 +65,22 @@ public SerializedClassType(CodeViewLeafKind kind, PdbReaderContext context, uint if (!_context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) || leaf is not SerializedFieldList list) { - _context.Parameters.ErrorListener.BadImage( - $"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + _context.Parameters.ErrorListener.BadImage($"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); return new FieldList(); } return list; } + + /// + protected override VTableShape? GetVTableShape() + { + if (_vTableShapeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_vTableShapeIndex, out var leaf) && leaf is VTableShape shape + ? shape + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid VTable shape index {_fieldIndex:X8}."); + } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs new file mode 100644 index 000000000..c2e7dc899 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVTableShape : VTableShape +{ + private readonly ushort _count; + private readonly BinaryStreamReader _entriesReader; + + /// + /// Reads a virtual function table shape from the provided input stream. + /// + /// The reading context in which the shape is situated in. + /// The index to assign to the shape. + /// The input stream to read from. + public SerializedVTableShape(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _count = reader.ReadUInt16(); + _entriesReader = reader; + } + + /// + protected override IList GetEntries() + { + var result = new List(_count); + var reader = _entriesReader.Fork(); + + // Entries are stored as 4-bit values. + byte currentByte = 0; + for (int i = 0; i < _count; i++) + { + if (i % 2 == 0) + { + currentByte = reader.ReadByte(); + result.Add((VTableShapeEntry) (currentByte & 0xF)); + } + else + { + result.Add((VTableShapeEntry) ((currentByte >> 8) & 0xF)); + } + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShape.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShape.cs new file mode 100644 index 000000000..75ab6425e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShape.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Describes the shape of the virtual function table of a class or structure type. +/// +public class VTableShape : CodeViewLeaf +{ + private IList? _entries; + + /// + /// Initializes a new empty virtual function table shape. + /// + /// The type index to assign to the shape. + protected VTableShape(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty virtual function table shape. + /// + public VTableShape() + : base(0) + { + } + + /// + /// Creates a new virtual function table shape with the provided entries. + /// + public VTableShape(params VTableShapeEntry[] entries) + : base(0) + { + _entries = new List(entries); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.VTShape; + + /// + /// Gets the list of entries that defines the shape of the virtual function table. + /// + public IList Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the list of entries stored in the shape. + /// + /// The entries. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs new file mode 100644 index 000000000..6384067b1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs @@ -0,0 +1,17 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible types that a single entry in a can be. +/// +public enum VTableShapeEntry : byte +{ +#pragma warning disable CS1591 + Near = 0x00, + Far = 0x01, + Thin = 0x02, + Outer = 0x03, + Meta = 0x04, + Near32 = 0x05, + Far32 = 0x06, +#pragma warning restore CS1591 +} From 9e5a9db01e8e126d1ccb3da108034fe6b645b3f4 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 11:23:31 +0200 Subject: [PATCH 064/182] BUGFIX: Shift by 4 instead of 8 for reading vtable shape entries. --- .../Leaves/Serialized/SerializedVTableShape.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs index c2e7dc899..6b8878eed 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs @@ -41,7 +41,7 @@ protected override IList GetEntries() } else { - result.Add((VTableShapeEntry) ((currentByte >> 8) & 0xF)); + result.Add((VTableShapeEntry) ((currentByte >> 4) & 0xF)); } } From b548e829f2bade8186d227d8f03e9b79f3e25630 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 11:31:31 +0200 Subject: [PATCH 065/182] Add type tests. --- .../Leaves/CodeViewLeaf.cs | 26 ++++---- .../Leaves/ClassTypeTest.cs | 61 +++++++++++++++++++ .../Leaves/EnumTypeTest.cs | 2 +- .../Leaves/FieldListTest.cs | 37 +++++++++++ .../Leaves/ModifierTypeTest.cs | 35 +++++++++++ .../Leaves/PointerTypeTest.cs | 60 ++++++++++++++++++ .../Leaves/VTableShapeTest.cs | 23 +++++++ 7 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index d7c22a20d..00997947c 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -65,16 +65,20 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex }; } - internal static object ReadNumeric(ref BinaryStreamReader reader) => (CodeViewLeafKind) reader.ReadUInt16() switch + internal static object ReadNumeric(ref BinaryStreamReader reader) { - < Numeric => (object) (uint) (CodeViewLeafKind) reader.ReadUInt16(), - Char => (char) reader.ReadByte(), - Short => reader.ReadInt16(), - UShort => reader.ReadUInt16(), - Long => reader.ReadInt32(), - ULong => reader.ReadUInt32(), - QuadWord => reader.ReadInt64(), - UQuadWord => reader.ReadUInt64(), - _ => 0 - }; + var kind = (CodeViewLeafKind) reader.ReadUInt16(); + return kind switch + { + < Numeric => (object) (uint) kind, + Char => (char) reader.ReadByte(), + Short => reader.ReadInt16(), + UShort => reader.ReadUInt16(), + Long => reader.ReadInt32(), + ULong => reader.ReadUInt32(), + QuadWord => reader.ReadInt64(), + UQuadWord => reader.ReadUInt64(), + _ => 0 + }; + } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeTest.cs new file mode 100644 index 000000000..670c60bfe --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeTest.cs @@ -0,0 +1,61 @@ +using System; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ClassTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ClassTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Theory] + [InlineData(CodeViewLeafKind.Class)] + [InlineData(CodeViewLeafKind.Structure)] + [InlineData(CodeViewLeafKind.Interface)] + public void CreateNewValidType(CodeViewLeafKind kind) + { + var type = new ClassType(kind, "MyType", "MyUniqueType", 4, StructureAttributes.FwdRef, null); + Assert.Equal(kind, type.LeafKind); + Assert.Equal("MyType", type.Name); + Assert.Equal("MyUniqueType", type.UniqueName); + Assert.Equal(4u, type.Size); + Assert.Equal(StructureAttributes.FwdRef, type.StructureAttributes); + Assert.Null(type.BaseType); + } + + [Fact] + public void CreateNonValidType() + { + Assert.Throws(() => new ClassType( + CodeViewLeafKind.Char, + "Invalid", + "Invalid", + 4, + StructureAttributes.FwdRef, + null)); + } + + [Fact] + public void ReadFieldList() + { + var type = (ClassType) _fixture.SimplePdb.GetLeafRecord(0x101b); + Assert.NotNull(type.Fields); + } + + [Fact] + public void ReadVTableShape() + { + var type = (ClassType) _fixture.SimplePdb.GetLeafRecord(0x239f); + Assert.NotNull(type.VTableShape); + Assert.Equal(new[] + { + VTableShapeEntry.Near32, + VTableShapeEntry.Near32, + }, type.VTableShape.Entries); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs index 60ce8a454..8a8a4b74c 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs @@ -18,7 +18,7 @@ public void FieldList() { var leaf = _fixture.SimplePdb.GetLeafRecord(0x1009); var fields = Assert.IsAssignableFrom(leaf).Fields.Entries.Cast().ToArray(); - var names = new Utf8String[] + var names = new[] { "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs new file mode 100644 index 000000000..120e73936 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs @@ -0,0 +1,37 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class FieldListTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public FieldListTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadEnumerateList() + { + var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x1008); + var enumerates = list.Entries + .Cast() + .Select(f => (f.Attributes, f.Name.Value, f.Value)) + .ToArray(); + + Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", 0u), + enumerates[0]); + Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", 1u), + enumerates[1]); + Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED", 2u), enumerates[2]); + Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST", 2u), + enumerates[3]); + Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST", 3u), + enumerates[4]); + Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32", '\xff'), + enumerates[5]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeTest.cs new file mode 100644 index 000000000..f4043eaa7 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeTest.cs @@ -0,0 +1,35 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ModifierTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ModifierTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void CreateNewType() + { + var type = new ModifierType(new SimpleType(SimpleTypeKind.Character8), ModifierAttributes.Const); + Assert.True(type.IsConst); + } + + [Fact] + public void ReadAttributes() + { + var type = (ModifierType) _fixture.SimplePdb.GetLeafRecord(0x1011); + Assert.True(type.IsConst); + } + + [Fact] + public void ReadBaseType() + { + var type = (ModifierType) _fixture.SimplePdb.GetLeafRecord(0x1011); + Assert.Equal(CodeViewLeafKind.Structure, type.BaseType.LeafKind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeTest.cs new file mode 100644 index 000000000..08aa946a9 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeTest.cs @@ -0,0 +1,60 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class PointerTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PointerTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void CreateNewType() + { + var type = new PointerType(new SimpleType(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + Assert.True(type.IsConst); + Assert.Equal(4, type.Size); + } + + [Fact] + public void UpdateKind() + { + var type = new PointerType(new SimpleType(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Kind = PointerAttributes.Near32; + Assert.Equal(PointerAttributes.Near32, type.Kind); + } + + [Fact] + public void UpdateMode() + { + var type = new PointerType(new SimpleType(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Mode = PointerAttributes.LValueReference; + Assert.Equal(PointerAttributes.LValueReference, type.Mode); + } + + [Fact] + public void UpdateSize() + { + var type = new PointerType(new SimpleType(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Size = 8; + Assert.Equal(8, type.Size); + } + + [Fact] + public void ReadAttributes() + { + var type = (PointerType) _fixture.SimplePdb.GetLeafRecord(0x1012); + Assert.True(type.IsNear32); + } + + [Fact] + public void ReadBaseType() + { + var type = (PointerType) _fixture.SimplePdb.GetLeafRecord(0x1012); + Assert.Equal(CodeViewLeafKind.Modifier, type.BaseType.LeafKind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeTest.cs new file mode 100644 index 000000000..ec5a1a005 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeTest.cs @@ -0,0 +1,23 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class VTableShapeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public VTableShapeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Theory] + [InlineData(0x2416, new[] { VTableShapeEntry.Near })] + [InlineData(0x239e, new[] { VTableShapeEntry.Near32,VTableShapeEntry.Near32 })] + public void ReadEntries(uint typeIndex, VTableShapeEntry[] expectedEntries) + { + var shape = (VTableShape) _fixture.SimplePdb.GetLeafRecord(typeIndex); + Assert.Equal(expectedEntries, shape.Entries); + } +} From 8b449ccdfd430387fa8af33fd0d198c13571bef4 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 11:55:09 +0200 Subject: [PATCH 066/182] Add read support for LF_MEMBER records. --- .../Leaves/CodeViewField.cs | 33 ++++++++++ .../Leaves/CodeViewLeaf.cs | 1 + .../Leaves/EnumerateField.cs | 31 +-------- .../Leaves/InstanceDataMember.cs | 66 +++++++++++++++++++ .../SerializedInstanceDataMember.cs | 49 ++++++++++++++ .../Leaves/FieldListTest.cs | 41 ++++++++---- 6 files changed, 180 insertions(+), 41 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataMember.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs index 85651a502..78fb608a3 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -5,6 +5,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public abstract class CodeViewField : CodeViewLeaf { + private readonly LazyVariable _name; + /// /// Initializes an empty CodeView field leaf. /// @@ -12,5 +14,36 @@ public abstract class CodeViewField : CodeViewLeaf protected CodeViewField(uint typeIndex) : base(typeIndex) { + _name = new LazyVariable(GetName); + } + + /// + /// Gets or sets the name of the field. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; } + + /// + /// Gets or sets the attributes associated to the field. + /// + public CodeViewFieldAttributes Attributes + { + get; + set; + } + + /// + /// Obtains the name of the field. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => Name; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 00997947c..4a632515d 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -57,6 +57,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), Interface => new SerializedClassType(Interface, context, typeIndex, dataReader), + Member => new SerializedInstanceDataMember(context, typeIndex, ref dataReader), Modifier => new SerializedModifierType(context, typeIndex, dataReader), Pointer => new SerializedPointerType(context, typeIndex, dataReader), Structure => new SerializedClassType(Structure, context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs index 7ccbe7380..c14ecb122 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs @@ -5,7 +5,6 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class EnumerateField : CodeViewField { - private readonly LazyVariable _name; private readonly LazyVariable _value; /// @@ -15,7 +14,6 @@ public class EnumerateField : CodeViewField protected EnumerateField(uint typeIndex) : base(typeIndex) { - _name = new LazyVariable(GetName); _value = new LazyVariable(GetValue); } @@ -28,7 +26,7 @@ protected EnumerateField(uint typeIndex) public EnumerateField(Utf8String name, object value, CodeViewFieldAttributes attributes) : base(0) { - _name = new LazyVariable(name); + Name = name; _value = new LazyVariable(value); Attributes = attributes; } @@ -36,15 +34,6 @@ public EnumerateField(Utf8String name, object value, CodeViewFieldAttributes att /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enumerate; - /// - /// Gets or sets the name of the enumerate field. - /// - public Utf8String Name - { - get => _name.Value; - set => _name.Value = value; - } - /// /// Gets or sets the constant value assigned to the field. /// @@ -54,24 +43,6 @@ public object Value set => _value.Value = value; } - /// - /// Gets or sets the attributes associated to the field. - /// - public CodeViewFieldAttributes Attributes - { - get; - set; - } - - /// - /// Obtains the name of the field. - /// - /// The name. - /// - /// This method is called upon initialization of the property. - /// - protected virtual Utf8String GetName() => Utf8String.Empty; - /// /// Obtains the value assigned to the field. /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs new file mode 100644 index 000000000..79ec0d20e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs @@ -0,0 +1,66 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents an instance data member in a class or structure type. +/// +public class InstanceDataMember : CodeViewField +{ + private readonly LazyVariable _dataType; + + /// + /// Initializes an empty instance data member. + /// + /// The type index to assign to the member. + protected InstanceDataMember(uint typeIndex) + : base(typeIndex) + { + _dataType = new LazyVariable(GetDataType); + } + + /// + /// Creates a new instance data member. + /// + /// The data type of the member. + /// The name of the member. + /// The byte offset within the class or structure that the member is stored at. + public InstanceDataMember(CodeViewType dataType, Utf8String name, ulong offset) + : base(0) + { + DataType = dataType; + Name = name; + Offset = offset; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Member; + + /// + /// Gets or sets the data type of the member. + /// + public CodeViewType DataType + { + get => _dataType.Value; + set => _dataType.Value = value; + } + + /// + /// Gets or sets the byte offset within the class or structure that the member is stored at. + /// + public ulong Offset + { + get; + set; + } + + /// + /// Obtains the data type of the member. + /// + /// The data type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetDataType() => null; + + /// + public override string ToString() => $"[{Offset:X4}]: {DataType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataMember.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataMember.cs new file mode 100644 index 000000000..44cb7ad43 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataMember.cs @@ -0,0 +1,49 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedInstanceDataMember : InstanceDataMember +{ + private readonly PdbReaderContext _context; + private readonly uint _dataTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an instance data member list from the provided input stream. + /// + /// The reading context in which the member is situated in. + /// The type index to assign to the member. + /// The input stream to read from. + public SerializedInstanceDataMember(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _dataTypeIndex = reader.ReadUInt32(); + + // We need to eagerly initialize the offset because it is the only way to know how large the leaf is. + Offset = Convert.ToUInt64(ReadNumeric(ref reader)); + + _context = context; + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewType? GetDataType() + { + if (_dataTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Instance data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}."); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs index 120e73936..18133682a 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs @@ -1,6 +1,7 @@ using System.Linq; using AsmResolver.Symbols.Pdb.Leaves; using Xunit; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewFieldAttributes; namespace AsmResolver.Symbols.Pdb.Tests.Leaves; @@ -22,16 +23,34 @@ public void ReadEnumerateList() .Select(f => (f.Attributes, f.Name.Value, f.Value)) .ToArray(); - Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", 0u), - enumerates[0]); - Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", 1u), - enumerates[1]); - Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED", 2u), enumerates[2]); - Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST", 2u), - enumerates[3]); - Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST", 3u), - enumerates[4]); - Assert.Equal((CodeViewFieldAttributes.Public, "DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32", '\xff'), - enumerates[5]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", 0u), enumerates[0]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", 1u), enumerates[1]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED", 2u), enumerates[2]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST", 2u), enumerates[3]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST", 3u), enumerates[4]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32", '\xff'), enumerates[5]); + } + + [Fact] + public void ReadInstanceDataMemberList() + { + var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x1017); + var enumerates = list.Entries + .Cast() + .Select(f => (f.Attributes, f.Name.Value, f.Offset)) + .ToArray(); + + Assert.Equal((Public, "cbSize", 0ul), enumerates[0]); + Assert.Equal((Public, "fMask", 4ul), enumerates[1]); + Assert.Equal((Public, "fType", 8ul), enumerates[2]); + Assert.Equal((Public, "fState", 12ul), enumerates[3]); + Assert.Equal((Public, "wID", 16ul), enumerates[4]); + Assert.Equal((Public, "hSubMenu", 20ul), enumerates[5]); + Assert.Equal((Public, "hbmpChecked", 24ul), enumerates[6]); + Assert.Equal((Public, "hbmpUnchecked", 28ul), enumerates[7]); + Assert.Equal((Public, "dwItemData", 32ul), enumerates[8]); + Assert.Equal((Public, "dwTypeData", 36ul), enumerates[9]); + Assert.Equal((Public, "cch", 40ul), enumerates[10]); + Assert.Equal((Public, "hbmpItem", 44ul), enumerates[11]); } } From 75e1172ee7d393384c3af788f9c42d80ca1c88ca Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 16:35:03 +0200 Subject: [PATCH 067/182] Add read support for LF_ARGLIST, LF_MFUNCTION, LF_METHODLIST and LF_METHOD. --- .../Leaves/ArgumentList.cs | 63 ++++++++ .../Leaves/CodeViewCallingConvention.cs | 132 +++++++++++++++++ .../Leaves/CodeViewFieldAttributes.cs | 30 ++++ .../Leaves/CodeViewLeaf.cs | 4 + .../Leaves/CodeViewLeafKind.cs | 2 +- .../Leaves/InstanceDataMember.cs | 2 +- .../Leaves/MemberFunction.cs | 136 ++++++++++++++++++ .../Leaves/MemberFunctionAttributes.cs | 25 ++++ .../Leaves/MethodList.cs | 54 +++++++ .../Leaves/MethodListEntry.cs | 94 ++++++++++++ .../Leaves/OverloadedMethod.cs | 48 +++++++ .../Serialized/SerializedArgumentList.cs | 50 +++++++ .../Leaves/Serialized/SerializedEnumType.cs | 10 +- .../Leaves/Serialized/SerializedFieldList.cs | 1 - .../Serialized/SerializedMemberFunction.cs | 75 ++++++++++ .../Leaves/Serialized/SerializedMethodList.cs | 38 +++++ .../Serialized/SerializedMethodListEntry.cs | 35 +++++ .../Serialized/SerializedOverloadedMethod.cs | 44 ++++++ .../Leaves/ArgumentListTest.cs | 23 +++ .../Leaves/FunctionTest.cs | 44 ++++++ .../Leaves/MethodListTest.cs | 33 +++++ 21 files changed, 933 insertions(+), 10 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/ArgumentList.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/MemberFunction.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/MethodList.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunction.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodList.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentList.cs new file mode 100644 index 000000000..ffa74b8cd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentList.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf containing a list of type arguments for a function or method. +/// +public class ArgumentList : CodeViewLeaf +{ + private IList? _types; + + /// + /// Initializes an empty argument list. + /// + /// The type index to assign to the list. + protected ArgumentList(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty argument list. + /// + public ArgumentList() + : base(0) + { + } + + /// + /// Creates a new argument list. + /// + public ArgumentList(params CodeViewType[] argumentTypes) + : base(0) + { + _types = new List(argumentTypes); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.ArgList; + + /// + /// Gets an ordered collection of types that correspond to the types of each parameter. + /// + public IList Types + { + get + { + if (_types is null) + Interlocked.CompareExchange(ref _types, GetArgumentTypes(), null); + return _types; + } + } + + /// + /// Obtains the argument types stored in the list. + /// + /// The types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetArgumentTypes() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs new file mode 100644 index 000000000..aacdc00f3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs @@ -0,0 +1,132 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all calling conventions that can be specified in a PDB image. +/// +public enum CodeViewCallingConvention : byte +{ + /// + /// near right to left push, caller pops stack + /// + NearC = 0x00, + + /// + /// far right to left push, caller pops stack + /// + FarC = 0x01, + + /// + /// near left to right push, callee pops stack + /// + NearPascal = 0x02, + + /// + /// far left to right push, callee pops stack + /// + FarPascal = 0x03, + + /// + /// near left to right push with regs, callee pops stack + /// + NearFast = 0x04, + + /// + /// far left to right push with regs, callee pops stack + /// + FarFast = 0x05, + + /// + /// skipped (unused) call index + /// + Skipped = 0x06, + + /// + /// near standard call + /// + NearStd = 0x07, + + /// + /// far standard call + /// + FarStd = 0x08, + + /// + /// near sys call + /// + NearSys = 0x09, + + /// + /// far sys call + /// + FarSys = 0x0a, + + /// + /// this call (this passed in register) + /// + ThisCall = 0x0b, + + /// + /// Mips call + /// + MipsCall = 0x0c, + + /// + /// Generic call sequence + /// + Generic = 0x0d, + + /// + /// Alpha call + /// + AlphaCall = 0x0e, + + /// + /// PPC call + /// + PpcCall = 0x0f, + + /// + /// Hitachi SuperH call + /// + ShCall = 0x10, + + /// + /// ARM call + /// + ArmCall = 0x11, + + /// + /// AM33 call + /// + Am33Call = 0x12, + + /// + /// TriCore Call + /// + TriCall = 0x13, + + /// + /// Hitachi SuperH-5 call + /// + Sh5Call = 0x14, + + /// + /// M32R Call + /// + M32RCall = 0x15, + + /// + /// clr call + /// + ClrCall = 0x16, + + /// + /// Marker for routines always inlined and thus lacking a convention + /// + Inline = 0x17, + + /// + /// near left to right push with regs, callee pops stack + /// + NearVector = 0x18, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs index 789f51108..efbea90ec 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs @@ -33,6 +33,36 @@ public enum CodeViewFieldAttributes : ushort /// AccessMask = 0b00000000_00000011, + /// + /// Indicates the method is a virtual method. + /// + Virtual = 0b00000000_00000100, + + /// + /// Indicates the method is a static method. + /// + Static = 0b00000000_00001000, + + /// + /// Indicates the method can be accessed only from within the current module. + /// + Friend = 0b00000000_00001100, + + /// + /// Indicates the method is a new introducing virtual method. + /// + IntroducingVirtual = 0b00000000_00010000, + + /// + /// Indicates the method is a pure virtual method. + /// + PureVirtual = 0b00000000_00010100, + + /// + /// Indicates the method is a new introducing pure virtual method. + /// + PureIntroducingVirtual = 0b00000000_00011000, + /// /// Provides the bit-mask that can be used to extract the method properties of the field. /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 4a632515d..e722fe4b9 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -52,12 +52,16 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { + ArgList => new SerializedArgumentList(context, typeIndex, dataReader), Class => new SerializedClassType(Class, context, typeIndex, dataReader), Enum => new SerializedEnumType(context, typeIndex, dataReader), Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), Interface => new SerializedClassType(Interface, context, typeIndex, dataReader), Member => new SerializedInstanceDataMember(context, typeIndex, ref dataReader), + Method => new SerializedOverloadedMethod(context, typeIndex, ref dataReader), + CodeViewLeafKind.MethodList => new SerializedMethodList(context, typeIndex, dataReader), + MFunction => new SerializedMemberFunction(context, typeIndex, dataReader), Modifier => new SerializedModifierType(context, typeIndex, dataReader), Pointer => new SerializedPointerType(context, typeIndex, dataReader), Structure => new SerializedClassType(Structure, context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs index cf156db17..a194e4b05 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs @@ -90,7 +90,7 @@ public enum CodeViewLeafKind : ushort // leaf indices starting records but referenced only from type records Skip = 0x1200, - Arglist = 0x1201, + ArgList = 0x1201, DefArgSt = 0x1202, FieldList = 0x1203, Derived = 0x1204, diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs index 79ec0d20e..e87a7f210 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs @@ -26,7 +26,7 @@ protected InstanceDataMember(uint typeIndex) public InstanceDataMember(CodeViewType dataType, Utf8String name, ulong offset) : base(0) { - DataType = dataType; + _dataType = new LazyVariable(dataType); Name = name; Offset = offset; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunction.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunction.cs new file mode 100644 index 000000000..4d5724678 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunction.cs @@ -0,0 +1,136 @@ +using System.Linq; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single instance member function. +/// +public class MemberFunction : CodeViewLeaf +{ + private readonly LazyVariable _returnType; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _thisType; + private readonly LazyVariable _argumentList; + + /// + /// Initializes an empty member function. + /// + /// The type index to assign to the function. + protected MemberFunction(uint typeIndex) + : base(typeIndex) + { + _returnType = new LazyVariable(GetReturnType); + _declaringType = new LazyVariable(GetDeclaringType); + _thisType = new LazyVariable(GetThisType); + _argumentList = new LazyVariable(GetArguments); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MFunction; + + /// + /// Gets or sets the return type of the function. + /// + public CodeViewType? ReturnType + { + get => _returnType.Value; + set => _returnType.Value = value; + } + + /// + /// Gets or sets the type that declares this member function. + /// + public CodeViewType? DeclaringType + { + get => _declaringType.Value; + set => _declaringType.Value = value; + } + + /// + /// Gets or sets the type of the this pointer that is used to access the member function. + /// + public CodeViewType? ThisType + { + get => _thisType.Value; + set => _thisType.Value = value; + } + + /// + /// Gets or sets the convention that is used when calling the member function. + /// + public CodeViewCallingConvention CallingConvention + { + get; + set; + } + + /// + /// Gets or sets the attributes associated to the function. + /// + public MemberFunctionAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the list of types of the parameters that this function defines. + /// + public ArgumentList? Arguments + { + get => _argumentList.Value; + set => _argumentList.Value = value; + } + + /// + /// Gets or sets the offset to adjust the this pointer with before devirtualization of this method. + /// + public uint ThisAdjuster + { + get; + set; + } + + /// + /// Obtains the return type of the function. + /// + /// The return type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetReturnType() => null; + + /// + /// Obtains the declaring type of the function. + /// + /// The declaring type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetDeclaringType() => null; + + /// + /// Obtains the this-type of the function. + /// + /// The this-type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetThisType() => null; + + /// + /// Obtains the argument types of the function. + /// + /// The argument types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ArgumentList? GetArguments() => null; + + /// + public override string ToString() + { + string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); + return $"{CallingConvention} {ReturnType} {DeclaringType}::*({args})"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs new file mode 100644 index 000000000..2fbcbb55c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs @@ -0,0 +1,25 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all attributes that can be assigned to a member function. +/// +[Flags] +public enum MemberFunctionAttributes : byte +{ + /// + /// Indicates if the function is a C++ style ReturnUDT function. + /// + CxxReturnUdt = 1, + + /// + /// Indicates the function is an instance constructor. + /// + Ctor = 2, + + /// + /// Indicates the function is an instance constructor of a class with virtual bases. + /// + CtorVBase = 4 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodList.cs new file mode 100644 index 000000000..83838059f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodList.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf record containing a list of overloaded methods. +/// +public class MethodList : CodeViewLeaf +{ + private IList? _entries; + + /// + /// Initializes an empty method list. + /// + /// The type index to assign to the list. + protected MethodList(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty method list. + /// + public MethodList() + : base(0) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MethodList; + + /// + /// Gets a collection of methods stored in the list. + /// + public IList Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the methods stored in the list. + /// + /// The methods + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs new file mode 100644 index 000000000..61bc5bb76 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs @@ -0,0 +1,94 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents one single entry in a list of overloaded methods. +/// +public class MethodListEntry +{ + private readonly LazyVariable _function; + + /// + /// Initializes an empty method list entry. + /// + protected MethodListEntry() + { + _function = new LazyVariable(GetFunction); + } + + /// + /// Creates a new method list entry. + /// + /// The attributes associated to this method. + /// The referenced function. + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunction function) + { + Attributes = attributes; + _function = new LazyVariable(function); + VFTableOffset = 0; + } + + /// + /// Creates a new method list entry. + /// + /// The attributes associated to this method. + /// The referenced function. + /// The offset to the slot the virtual function table that this method occupies. + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunction function, uint vfTableOffset) + { + Attributes = attributes; + _function = new LazyVariable(function); + VFTableOffset = vfTableOffset; + } + + /// + /// Gets or sets the attributes associated to this method. + /// + public CodeViewFieldAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the function that is referenced by this method. + /// + public MemberFunction? Function + { + get => _function.Value; + set => _function.Value = value; + } + + /// + /// Gets a value indicating whether the function is a newly introduced virtual function. + /// + public bool IsIntroducingVirtual => + (Attributes & CodeViewFieldAttributes.IntroducingVirtual) != 0 + || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) != 0; + + /// + /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function + /// table that this method occupies. + /// + public uint VFTableOffset + { + get; + set; + } + + /// + /// Obtains the function that this method references. + /// + /// The function. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MemberFunction? GetFunction() => null; + + /// + public override string ToString() + { + return IsIntroducingVirtual + ? $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}, {nameof(VFTableOffset)}: {VFTableOffset}" + : $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs new file mode 100644 index 000000000..662e2673c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs @@ -0,0 +1,48 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a method that is overloaded by one or more functions. +/// +public class OverloadedMethod : CodeViewField +{ + private readonly LazyVariable _methods; + + /// + /// Creates a new empty overloaded method. + /// + /// The type index to assign to the method. + protected OverloadedMethod(uint typeIndex) + : base(typeIndex) + { + _methods = new LazyVariable(GetMethods); + } + + /// + /// Creates a new empty overloaded method. + /// + public OverloadedMethod() + : this(0) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Method; + + /// + /// Gets or sets a list of methods that were overloaded. + /// + public MethodList? Methods + { + get => _methods.Value; + set => _methods.Value = value; + } + + /// + /// Obtains the list of methods that were overloaded. + /// + /// The methods. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MethodList? GetMethods() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs new file mode 100644 index 000000000..8c16d644d --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedArgumentList : ArgumentList +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a argument list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedArgumentList(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + protected override IList GetArgumentTypes() + { + var reader = _reader.Fork(); + + int count = reader.ReadInt32(); + var result = new List(count); + + for (int i = 0; i < count; i++) + { + uint typeIndex = reader.ReadUInt32(); + if (!_context.ParentImage.TryGetLeafRecord(typeIndex, out var leaf) || leaf is not CodeViewType t) + { + _context.Parameters.ErrorListener.BadImage( + $"Argument list {TypeIndex:X8} contains an invalid argument type index {typeIndex:X8}."); + return result; + } + + result.Add(t); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs index 542153c63..8427b3d0c 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs @@ -48,13 +48,9 @@ public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStream if (_fieldIndex == 0) return null; - if (!_context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) || leaf is not SerializedFieldList list) - { - _context.Parameters.ErrorListener.BadImage( + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is FieldList list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Enum type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); - return new FieldList(); - } - - return list; } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs index b2b558efb..712f8cd55 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs @@ -22,7 +22,6 @@ public SerializedFieldList(PdbReaderContext context, uint typeIndex, BinaryStrea { _context = context; _reader = reader; - reader.RelativeOffset = reader.Length; } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunction.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunction.cs new file mode 100644 index 000000000..f3aacd7e3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunction.cs @@ -0,0 +1,75 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMemberFunction : MemberFunction +{ + private readonly PdbReaderContext _context; + private readonly uint _returnTypeIndex; + private readonly uint _declaringTypeIndex; + private readonly uint _thisTypeIndex; + private readonly ushort _parameterCount; + private readonly uint _argumentListIndex; + + /// + /// Reads a member function from the provided input stream. + /// + /// The reading context in which the function is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedMemberFunction(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _returnTypeIndex = reader.ReadUInt32(); + _declaringTypeIndex = reader.ReadUInt32(); + _thisTypeIndex = reader.ReadUInt32(); + CallingConvention = (CodeViewCallingConvention) reader.ReadByte(); + Attributes = (MemberFunctionAttributes) reader.ReadByte(); + _parameterCount = reader.ReadUInt16(); + _argumentListIndex = reader.ReadUInt32(); + ThisAdjuster = reader.ReadUInt32(); + } + + /// + protected override CodeViewType? GetReturnType() + { + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); + } + + /// + protected override CodeViewType? GetDeclaringType() + { + return _context.ParentImage.TryGetLeafRecord(_declaringTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid declaring type index {_declaringTypeIndex:X8}."); + } + + /// + protected override CodeViewType? GetThisType() + { + return _context.ParentImage.TryGetLeafRecord(_thisTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid this-type index {_thisTypeIndex:X8}."); + } + + /// + protected override ArgumentList? GetArguments() + { + if (_argumentListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentList list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodList.cs new file mode 100644 index 000000000..0d42631c1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodList.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMethodList : MethodList +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a method list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedMethodList(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + protected override IList GetEntries() + { + var result = new List(); + + var reader = _reader.Fork(); + while (reader.CanRead(8)) + result.Add(new SerializedMethodListEntry(_context, ref reader)); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs new file mode 100644 index 000000000..df97c21cc --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMethodListEntry : MethodListEntry +{ + private readonly PdbReaderContext _context; + private readonly uint _functionIndex; + + /// + /// Reads a member function from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The input stream to read from. + public SerializedMethodListEntry(PdbReaderContext context, ref BinaryStreamReader reader) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + reader.ReadUInt16(); // padding + _functionIndex = reader.ReadUInt32(); + VFTableOffset = IsIntroducingVirtual ? reader.ReadUInt32() : 0; + } + + /// + protected override MemberFunction? GetFunction() + { + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunction type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Method list entry contains an invalid return type index {_functionIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs new file mode 100644 index 000000000..41730184a --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs @@ -0,0 +1,44 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedOverloadedMethod : OverloadedMethod +{ + private readonly PdbReaderContext _context; + private readonly uint _functionCount; + private readonly uint _methodListIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an overloaded method from the provided input stream. + /// + /// The reading context in which the method is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedOverloadedMethod(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _functionCount = reader.ReadUInt32(); + _methodListIndex = reader.ReadUInt32(); + _nameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override MethodList? GetMethods() + { + if (_methodListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_methodListIndex, out var leaf) && leaf is MethodList list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Overloaded method {TypeIndex:X8} contains an invalid field list index {_methodListIndex:X8}."); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListTest.cs new file mode 100644 index 000000000..e4731ad9d --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListTest.cs @@ -0,0 +1,23 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ArgumentListTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ArgumentListTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadMultipleTypes() + { + var list = (ArgumentList) _fixture.SimplePdb.GetLeafRecord(0x2391); + Assert.IsAssignableFrom(list.Types[0]); + Assert.IsAssignableFrom(list.Types[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionTest.cs new file mode 100644 index 000000000..21f6a7a01 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionTest.cs @@ -0,0 +1,44 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class FunctionTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public FunctionTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadReturnType() + { + var function = (MemberFunction) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(function.ReturnType).Kind); + } + + [Fact] + public void ReadDeclaringType() + { + var function = (MemberFunction) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.Equal("std::bad_cast", Assert.IsAssignableFrom(function.DeclaringType).Name); + } + + [Fact] + public void ReadNonNullThisType() + { + var function = (MemberFunction) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.IsAssignableFrom(function.ThisType); + } + + [Fact] + public void ReadArgumentList() + { + var function = (MemberFunction) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.NotNull(function.Arguments); + Assert.IsAssignableFrom(function.Arguments!.Types[0]); + Assert.IsAssignableFrom(function.Arguments.Types[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListTest.cs new file mode 100644 index 000000000..73003207b --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListTest.cs @@ -0,0 +1,33 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewFieldAttributes; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class MethodListTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public MethodListTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadNonIntroVirtualEntries() + { + var list = (MethodList) _fixture.SimplePdb.GetLeafRecord(0x2394); + var entries = list.Entries; + + Assert.Equal(new[] + { + Public | CompilerGenerated, + Public | CompilerGenerated, + Private, + Public, + }, entries.Select(e => e.Attributes)); + + Assert.All(entries, Assert.NotNull); + } +} From c367a8fe4dc0f1aadf7388ac040a9dc9d2db843d Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 16:52:55 +0200 Subject: [PATCH 068/182] Add read support for LF_ONEMETHOD records. --- .../Leaves/CodeViewField.cs | 7 ++ .../Leaves/CodeViewLeaf.cs | 1 + .../Leaves/NonOverloadedMethod.cs | 80 +++++++++++++++++++ .../Leaves/OverloadedMethod.cs | 2 +- .../SerializedNonOverloadedMethod.cs | 33 ++++++++ 5 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs index 78fb608a3..b3e5fc784 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -35,6 +35,13 @@ public CodeViewFieldAttributes Attributes set; } + /// + /// Gets a value indicating whether the field is a newly introduced virtual function. + /// + public bool IsIntroducingVirtual => + (Attributes & CodeViewFieldAttributes.IntroducingVirtual) != 0 + || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) != 0; + /// /// Obtains the name of the field. /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index e722fe4b9..32566b4ab 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -63,6 +63,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex CodeViewLeafKind.MethodList => new SerializedMethodList(context, typeIndex, dataReader), MFunction => new SerializedMemberFunction(context, typeIndex, dataReader), Modifier => new SerializedModifierType(context, typeIndex, dataReader), + OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader), Pointer => new SerializedPointerType(context, typeIndex, dataReader), Structure => new SerializedClassType(Structure, context, typeIndex, dataReader), VTShape => new SerializedVTableShape(context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs new file mode 100644 index 000000000..746f96090 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs @@ -0,0 +1,80 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single method in a type. +/// +public class NonOverloadedMethod : CodeViewField +{ + private readonly LazyVariable _function; + + /// + /// Initializes an empty non-overloaded method. + /// + /// The type index to assign to the method. + protected NonOverloadedMethod(uint typeIndex) + : base(typeIndex) + { + _function = new LazyVariable(GetFunction); + } + + /// + /// Creates a new overloaded method. + /// + /// The function that is referenced by the method. + /// The attributes associated to the method. + /// The name of the method. + public NonOverloadedMethod(MemberFunction function, CodeViewFieldAttributes attributes, Utf8String name) + : base(0) + { + _function = new LazyVariable(function); + Attributes = attributes; + Name = name; + } + + /// + /// Creates a new overloaded method. + /// + /// The function that is referenced by the method. + /// The attributes associated to the method. + /// The name of the method. + /// The offset to the slot the virtual function table that this method occupies. + public NonOverloadedMethod(MemberFunction function, CodeViewFieldAttributes attributes, Utf8String name, uint vfTableOffset) + : base(0) + { + _function = new LazyVariable(function); + Attributes = attributes; + Name = name; + VFTableOffset = vfTableOffset; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.OneMethod; + + /// + /// Gets or sets the function that is referenced by this method. + /// + public MemberFunction? Function + { + get => _function.Value; + set => _function.Value = value; + } + + /// + /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function + /// table that this method occupies. + /// + public uint VFTableOffset + { + get; + set; + } + + /// + /// Obtains the function that this method references. + /// + /// The function. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MemberFunction? GetFunction() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs index 662e2673c..5ac395c5b 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs @@ -8,7 +8,7 @@ public class OverloadedMethod : CodeViewField private readonly LazyVariable _methods; /// - /// Creates a new empty overloaded method. + /// Initializes an empty overloaded method. /// /// The type index to assign to the method. protected OverloadedMethod(uint typeIndex) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs new file mode 100644 index 000000000..f6782445e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs @@ -0,0 +1,33 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedNonOverloadedMethod : NonOverloadedMethod +{ + private readonly PdbReaderContext _context; + private readonly uint _functionIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a non-overloaded method from the provided input stream. + /// + /// The reading context in which the method is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedNonOverloadedMethod(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _functionIndex = reader.ReadUInt32(); + if (IsIntroducingVirtual) + VFTableOffset = reader.ReadUInt32(); + _nameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} From a273559389b7b7848d5cbcda77e5cb7f7e155949 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Jul 2022 17:19:05 +0200 Subject: [PATCH 069/182] Add read support LF_BCLASS records. --- .../Leaves/BaseClass.cs | 52 +++++++++++++++++++ .../Leaves/CodeViewField.cs | 4 +- .../Leaves/CodeViewLeaf.cs | 1 + .../Serialized/SerializedArgumentList.cs | 2 +- .../Leaves/Serialized/SerializedBaseClass.cs | 37 +++++++++++++ .../Leaves/Serialized/SerializedFieldList.cs | 8 ++- .../SerializedNonOverloadedMethod.cs | 1 + .../Serialized/SerializedOverloadedMethod.cs | 5 +- .../Leaves/FieldListTest.cs | 15 ++++++ 9 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/BaseClass.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClass.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClass.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClass.cs new file mode 100644 index 000000000..028e1d244 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClass.cs @@ -0,0 +1,52 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a reference to a base class object in a structure. +/// +public class BaseClass : CodeViewField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty base class. + /// + /// The type index to assign to the base class field. + protected BaseClass(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetBaseType); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BClass; + + /// + /// Gets or sets the base type that this base class is referencing. + /// + public CodeViewType? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the offset of the base within the class. + /// + public ulong Offset + { + get; + set; + } + + /// + /// Obtains the base type that the class is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetBaseType() => null; + + /// + public override string ToString() => Type?.ToString() ?? "<<>>"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs index b3e5fc784..141023404 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -39,8 +39,8 @@ public CodeViewFieldAttributes Attributes /// Gets a value indicating whether the field is a newly introduced virtual function. /// public bool IsIntroducingVirtual => - (Attributes & CodeViewFieldAttributes.IntroducingVirtual) != 0 - || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) != 0; + (Attributes & CodeViewFieldAttributes.IntroducingVirtual) == CodeViewFieldAttributes.IntroducingVirtual + || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) == CodeViewFieldAttributes.PureIntroducingVirtual; /// /// Obtains the name of the field. diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 32566b4ab..73189b742 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -53,6 +53,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex return kind switch { ArgList => new SerializedArgumentList(context, typeIndex, dataReader), + BClass => new SerializedBaseClass(context, typeIndex, ref dataReader), Class => new SerializedClassType(Class, context, typeIndex, dataReader), Enum => new SerializedEnumType(context, typeIndex, dataReader), Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs index 8c16d644d..2663b7f24 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs @@ -15,7 +15,7 @@ public class SerializedArgumentList : ArgumentList /// Reads a argument list from the provided input stream. /// /// The reading context in which the list is situated in. - /// The type index to assign to the enum type. + /// The type index to assign to the list. /// The input stream to read from. public SerializedArgumentList(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClass.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClass.cs new file mode 100644 index 000000000..997ba43ab --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClass.cs @@ -0,0 +1,37 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedBaseClass : BaseClass +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a base class from the provided input stream. + /// + /// The reading context in which the class is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedBaseClass(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _baseTypeIndex = reader.ReadUInt32(); + Offset = Convert.ToUInt64(ReadNumeric(ref reader)); + } + + /// + protected override CodeViewType? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Base class {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs index 712f8cd55..8fafa97fe 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs @@ -35,15 +35,19 @@ protected override IList GetEntries() while (reader.CanRead(sizeof(ushort))) { // Skip padding bytes. - while (true) + while (reader.CanRead(sizeof(byte))) { - if ((CodeViewLeafKind) reader.ReadByte() < CodeViewLeafKind.Pad0) + var b = (CodeViewLeafKind) reader.ReadByte(); + if (b < CodeViewLeafKind.Pad0) { reader.Offset--; break; } } + if (!reader.CanRead(sizeof(byte))) + break; + // Read field. var leaf = FromReaderNoHeader(_context, 0, ref reader); if (leaf is CodeViewField field) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs index f6782445e..96f10b517 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs @@ -26,6 +26,7 @@ public SerializedNonOverloadedMethod(PdbReaderContext context, uint typeIndex, r if (IsIntroducingVirtual) VFTableOffset = reader.ReadUInt32(); _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs index 41730184a..a92f37e97 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs @@ -8,7 +8,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; public class SerializedOverloadedMethod : OverloadedMethod { private readonly PdbReaderContext _context; - private readonly uint _functionCount; + private readonly ushort _functionCount; private readonly uint _methodListIndex; private readonly BinaryStreamReader _nameReader; @@ -22,9 +22,10 @@ public SerializedOverloadedMethod(PdbReaderContext context, uint typeIndex, ref : base(typeIndex) { _context = context; - _functionCount = reader.ReadUInt32(); + _functionCount = reader.ReadUInt16(); _methodListIndex = reader.ReadUInt32(); _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); } /// diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs index 18133682a..73c0fae57 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs @@ -53,4 +53,19 @@ public void ReadInstanceDataMemberList() Assert.Equal((Public, "cch", 40ul), enumerates[10]); Assert.Equal((Public, "hbmpItem", 44ul), enumerates[11]); } + + [Fact] + public void ReadMixed() + { + var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x239d); + + Assert.Equal("std::exception", Assert.IsAssignableFrom( + Assert.IsAssignableFrom(list.Entries[0]).Type).Name); + Assert.Equal("bad_cast", Assert.IsAssignableFrom(list.Entries[1]).Name); + Assert.Equal("__construct_from_string_literal", Assert.IsAssignableFrom(list.Entries[2]).Name); + Assert.Equal("~bad_cast", Assert.IsAssignableFrom(list.Entries[3]).Name); + Assert.Equal("operator=", Assert.IsAssignableFrom(list.Entries[4]).Name); + Assert.Equal("__local_vftable_ctor_closure", Assert.IsAssignableFrom(list.Entries[5]).Name); + Assert.Equal("__vecDelDtor", Assert.IsAssignableFrom(list.Entries[6]).Name); + } } From eee0f2cf642776c757965877c14abdb5b3fc0011 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 30 Jul 2022 14:00:39 +0200 Subject: [PATCH 070/182] Add read support LF_NESTTYPE and LF_NESTTYPEEX records. --- .../Leaves/CodeViewLeaf.cs | 1 + .../Leaves/NestedType.cs | 69 +++++++++++++++++++ .../Leaves/Serialized/SerializedNestedType.cs | 41 +++++++++++ .../Leaves/FieldListTest.cs | 13 +++- 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/NestedType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedType.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 73189b742..cb2a2b6ee 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -64,6 +64,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex CodeViewLeafKind.MethodList => new SerializedMethodList(context, typeIndex, dataReader), MFunction => new SerializedMemberFunction(context, typeIndex, dataReader), Modifier => new SerializedModifierType(context, typeIndex, dataReader), + NestType or NestTypeEx => new SerializedNestedType(context, typeIndex, ref dataReader), OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader), Pointer => new SerializedPointerType(context, typeIndex, dataReader), Structure => new SerializedClassType(Structure, context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NestedType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NestedType.cs new file mode 100644 index 000000000..33a1ecb02 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NestedType.cs @@ -0,0 +1,69 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a field in a type that references a nested type definition. +/// +public class NestedType : CodeViewField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty nested type. + /// + /// The type index to assign to the nested type field. + protected NestedType(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetNestedType); + } + + /// + /// Creates a new nested type field. + /// + /// The definition of the nested type + /// The name of the nested type. + public NestedType(CodeViewType type, Utf8String name) + : base(0) + { + _type = new LazyVariable(type); + Name = name; + Attributes = 0; + } + + /// + /// Creates a new nested type (extended) field. + /// + /// The definition of the nested type + /// The name of the nested type. + /// The attributes assigned to the type. + public NestedType(CodeViewType type, Utf8String name, CodeViewFieldAttributes attributes) + : base(0) + { + _type = new LazyVariable(type); + Name = name; + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => Attributes == 0 + ? CodeViewLeafKind.NestType + : CodeViewLeafKind.NestTypeEx; + + /// + /// Gets or sets the definition of the referenced nested type. + /// + public CodeViewType? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Obtains the definition of the nested type. + /// + /// The type + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetNestedType() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedType.cs new file mode 100644 index 000000000..7e22a80a4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedType.cs @@ -0,0 +1,41 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedNestedType : NestedType +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a nested type field from the provided input stream. + /// + /// The reading context in which the field is situated in. + /// The index to assign to the field. + /// The input stream to read from. + public SerializedNestedType(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _typeIndex = reader.ReadUInt32(); + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewType? GetNestedType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Nested type {TypeIndex:X8} contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs index 73c0fae57..88f62e4ab 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs @@ -55,7 +55,7 @@ public void ReadInstanceDataMemberList() } [Fact] - public void ReadMixed() + public void ReadMethodsAndBaseClass() { var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x239d); @@ -68,4 +68,15 @@ public void ReadMixed() Assert.Equal("__local_vftable_ctor_closure", Assert.IsAssignableFrom(list.Entries[5]).Name); Assert.Equal("__vecDelDtor", Assert.IsAssignableFrom(list.Entries[6]).Name); } + + [Fact] + public void ReadNestedTypes() + { + var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x1854); + + Assert.Equal("_LDT_ENTRY::::", + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[0]).Type).Name); + Assert.Equal("_LDT_ENTRY::::", + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[2]).Type).Name); + } } From bb3d977997c09ded17daa2768d4888aa7dde58a3 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 30 Jul 2022 14:22:53 +0200 Subject: [PATCH 071/182] Extract CodeViewCompositeType base class and read support LF_UNION. --- .../Leaves/ClassType.cs | 2 +- ...omplexType.cs => CodeViewCompositeType.cs} | 42 ++++--------- .../Leaves/CodeViewDerivedType.cs | 37 +++++++++++ .../Leaves/CodeViewLeaf.cs | 1 + .../Leaves/EnumType.cs | 2 +- .../Leaves/Serialized/SerializedClassType.cs | 11 ++-- .../Leaves/Serialized/SerializedUnionType.cs | 55 +++++++++++++++++ .../Leaves/UnionType.cs | 61 +++++++++++++++++++ .../Leaves/UnionTypeTest.cs | 47 ++++++++++++++ 9 files changed, 218 insertions(+), 40 deletions(-) rename src/AsmResolver.Symbols.Pdb/Leaves/{CodeViewComplexType.cs => CodeViewCompositeType.cs} (62%) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/UnionType.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs index fb1f8c2ed..3eac1e127 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a class, structure or interface type in a PDB. /// -public class ClassType : CodeViewComplexType +public class ClassType : CodeViewDerivedType { private readonly LazyVariable _uniqueName; private readonly LazyVariable _vtableShape; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewComplexType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs similarity index 62% rename from src/AsmResolver.Symbols.Pdb/Leaves/CodeViewComplexType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs index 13ce72f52..545993886 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewComplexType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs @@ -1,32 +1,24 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// -/// Provides a base for all custom types that may define fields and/or are derived from a base type. +/// Provides a base for all code view types that can define one or more fields. /// -public abstract class CodeViewComplexType : CodeViewType +public abstract class CodeViewCompositeType : CodeViewType { private readonly LazyVariable _name; - private readonly LazyVariable _baseType; private readonly LazyVariable _fields; - /// - protected CodeViewComplexType(uint typeIndex) + /// + /// Initializes a new empty composite type. + /// + /// The type index to assign to the type. + protected CodeViewCompositeType(uint typeIndex) : base(typeIndex) { _name = new LazyVariable(GetName); - _baseType = new LazyVariable(GetBaseType); _fields = new LazyVariable(GetFields); } - /// - /// Gets or sets the name of the enum type. - /// - public Utf8String Name - { - get => _name.Value; - set => _name.Value = value; - } - /// /// Gets or sets the structural attributes assigned to the type. /// @@ -37,12 +29,12 @@ public StructureAttributes StructureAttributes } /// - /// Gets or sets the base type that this type is deriving from. + /// Gets or sets the name of the enum type. /// - public CodeViewType? BaseType + public Utf8String Name { - get => _baseType.Value; - set => _baseType.Value = value; + get => _name.Value; + set => _name.Value = value; } /// @@ -63,15 +55,6 @@ public StructureAttributes StructureAttributes /// protected virtual Utf8String GetName() => Utf8String.Empty; - /// - /// Obtains the type that the type is derived from. - /// - /// The type. - /// - /// This method is called upon initialization of the property. - /// - protected virtual CodeViewType? GetBaseType() => null; - /// /// Obtains the fields defined in the type. /// @@ -80,7 +63,4 @@ public StructureAttributes StructureAttributes /// This method is called upon initialization of the property. /// protected virtual FieldList? GetFields() => null; - - /// - public override string ToString() => Name; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedType.cs new file mode 100644 index 000000000..f73a8ce21 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedType.cs @@ -0,0 +1,37 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides a base for all custom types that may be derived from a base type. +/// +public abstract class CodeViewDerivedType : CodeViewCompositeType +{ + private readonly LazyVariable _baseType; + + /// + protected CodeViewDerivedType(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Gets or sets the base type that this type is deriving from. + /// + public CodeViewType? BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Obtains the type that the type is derived from. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetBaseType() => null; + + /// + public override string ToString() => Name; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index cb2a2b6ee..a9e54e08d 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -68,6 +68,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader), Pointer => new SerializedPointerType(context, typeIndex, dataReader), Structure => new SerializedClassType(Structure, context, typeIndex, dataReader), + Union => new SerializedUnionType(context, typeIndex, dataReader), VTShape => new SerializedVTableShape(context, typeIndex, dataReader), _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) }; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs index 1c9e9e0b8..f2b677155 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents an enum type. /// -public class EnumType : CodeViewComplexType +public class EnumType : CodeViewDerivedType { /// /// Initializes a new empty enum type. diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs index 4fe10e895..9f47f719c 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs @@ -63,13 +63,10 @@ public SerializedClassType(CodeViewLeafKind kind, PdbReaderContext context, uint if (_fieldIndex == 0) return null; - if (!_context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) || leaf is not SerializedFieldList list) - { - _context.Parameters.ErrorListener.BadImage($"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); - return new FieldList(); - } - - return list; + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldList list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionType.cs new file mode 100644 index 000000000..dda21c034 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionType.cs @@ -0,0 +1,55 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedUnionType : UnionType +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _fieldIndex; + private readonly BinaryStreamReader _nameReader; + private readonly BinaryStreamReader _uniqueNameReader; + + /// + /// Reads a union type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedUnionType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _fieldIndex = reader.ReadUInt32(); + + Size = Convert.ToUInt64(ReadNumeric(ref reader)); + + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + _uniqueNameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String(); + + /// + protected override FieldList? GetFields() + { + if (_fieldIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldList list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Union type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnionType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnionType.cs new file mode 100644 index 000000000..4f4173b15 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnionType.cs @@ -0,0 +1,61 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type of a value that may have several representations or formats within the same position of memory. +/// +public class UnionType : CodeViewCompositeType +{ + private readonly LazyVariable _uniqueName; + + /// + /// Initializes an empty union type. + /// + /// The type index to assign to the union. + protected UnionType(uint typeIndex) + : base(typeIndex) + { + _uniqueName = new LazyVariable(GetUniqueName); + } + + /// + /// Creates a new union type with the provided size. + /// + /// The total size in bytes of the union. + public UnionType(ulong size) + : base(0) + { + _uniqueName = new LazyVariable(Utf8String.Empty); + Size = size; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Union; + + /// + /// Gets or sets the total size in bytes of the union type. + /// + public ulong Size + { + get; + set; + } + + /// + /// Gets or sets the uniquely identifiable name for this type. + /// + public Utf8String UniqueName + { + get => _uniqueName.Value; + set => _uniqueName.Value = value; + } + + + /// + /// Obtains the uniquely identifiable name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetUniqueName() => null; +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeTest.cs new file mode 100644 index 000000000..2279f08ac --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeTest.cs @@ -0,0 +1,47 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class UnionTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public UnionTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadSize() + { + var type = (UnionType) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal(4ul, type.Size); + } + + [Fact] + public void ReadName() + { + var type = (UnionType) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal("_LDT_ENTRY::", type.Name); + } + + [Fact] + public void ReadUniqueName() + { + var type = (UnionType) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal(".?AT@_LDT_ENTRY@@", type.UniqueName); + } + + [Fact] + public void ReadFieldList() + { + var type = (UnionType) _fixture.SimplePdb.GetLeafRecord(0x1855); + var fields = type.Fields!; + Assert.NotNull(fields); + Assert.IsAssignableFrom(fields.Entries[0]); + Assert.IsAssignableFrom(fields.Entries[1]); + Assert.IsAssignableFrom(fields.Entries[2]); + Assert.IsAssignableFrom(fields.Entries[3]); + } +} From 90bc833c64eb18094d6f424d57dec888efdfbe59 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 30 Jul 2022 14:40:32 +0200 Subject: [PATCH 072/182] Add read support LF_ARRAY records. --- .../Leaves/ArrayType.cs | 123 ++++++++++++++++++ .../Leaves/CodeViewCompositeType.cs | 2 +- .../Leaves/CodeViewLeaf.cs | 1 + .../Leaves/Serialized/SerializedArrayType.cs | 52 ++++++++ .../Leaves/ArrayTypeTest.cs | 42 ++++++ 5 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/ArrayType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayType.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArrayType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayType.cs new file mode 100644 index 000000000..9fd14ec62 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayType.cs @@ -0,0 +1,123 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type describing an array of elements. +/// +public class ArrayType : CodeViewType +{ + private readonly LazyVariable _elementType; + private readonly LazyVariable _indexType; + private readonly LazyVariable _name; + + /// + /// Initializes a new empty array type. + /// + /// The type index to assign to the type. + protected ArrayType(uint typeIndex) + : base(typeIndex) + { + _elementType = new LazyVariable(GetElementType); + _indexType = new LazyVariable(GetIndexType); + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new array type. + /// + /// The type of each element in the array. + /// The type to use for indexing into the array. + /// The number of elements in the array. + public ArrayType(CodeViewType elementType, CodeViewType indexType, ulong length) + : base(0) + { + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); + Length = length; + _name = new LazyVariable(Utf8String.Empty); + } + + /// + /// Creates a new array type. + /// + /// The type of each element in the array. + /// The type to use for indexing into the array. + /// The number of elements in the array. + /// The name of the array type. + public ArrayType(CodeViewType elementType, CodeViewType indexType, ulong length, Utf8String name) + : base(0) + { + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); + Length = length; + _name = new LazyVariable(Name); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Array; + + /// + /// Gets or sets the type of each element in the array. + /// + public CodeViewType? ElementType + { + get => _elementType.Value; + set => _elementType.Value = value; + } + + /// + /// Gets or sets the type that is used to index into the array. + /// + public CodeViewType? IndexType + { + get => _indexType.Value; + set => _indexType.Value = value; + } + + /// + /// Gets or sets the number of elements in the array. + /// + public ulong Length + { + get; + set; + } + + /// + /// Gets or sets the name of the type. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the element type of the array. + /// + /// The element type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetElementType() => null; + + /// + /// Obtains the index type of the array. + /// + /// The index type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetIndexType() => null; + + /// + /// Obtains the name type of the array. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => $"{ElementType}[{Length}]"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs index 545993886..d499fa06e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs @@ -29,7 +29,7 @@ public StructureAttributes StructureAttributes } /// - /// Gets or sets the name of the enum type. + /// Gets or sets the name of the type. /// public Utf8String Name { diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index a9e54e08d..53e915985 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -52,6 +52,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { + Array => new SerializedArrayType(context, typeIndex, dataReader), ArgList => new SerializedArgumentList(context, typeIndex, dataReader), BClass => new SerializedBaseClass(context, typeIndex, ref dataReader), Class => new SerializedClassType(Class, context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayType.cs new file mode 100644 index 000000000..d5ab123a8 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayType.cs @@ -0,0 +1,52 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedArrayType : ArrayType +{ + private readonly PdbReaderContext _context; + private readonly uint _elementTypeIndex; + private readonly uint _indexTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an array type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedArrayType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _elementTypeIndex = reader.ReadUInt32(); + _indexTypeIndex = reader.ReadUInt32(); + Length = Convert.ToUInt64(ReadNumeric(ref reader)); + _nameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewType? GetElementType() + { + return _context.ParentImage.TryGetLeafRecord(_elementTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Array type {TypeIndex:X8} contains an invalid element type index {_elementTypeIndex:X8}."); + } + + /// + protected override CodeViewType? GetIndexType() + { + return _context.ParentImage.TryGetLeafRecord(_indexTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Array type {TypeIndex:X8} contains an invalid index type index {_indexTypeIndex:X8}."); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeTest.cs new file mode 100644 index 000000000..4202a53e0 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeTest.cs @@ -0,0 +1,42 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ArrayTypeTest: IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ArrayTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadElementType() + { + var type = (ArrayType) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(type.ElementType).Kind); + } + + [Fact] + public void ReadIndexType() + { + var type = (ArrayType) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(SimpleTypeKind.UInt32Long, Assert.IsAssignableFrom(type.IndexType).Kind); + } + + [Fact] + public void ReadLength() + { + var type = (ArrayType) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(4u, type.Length); + } + + [Fact] + public void ReadEmptyName() + { + var type = (ArrayType) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.True(Utf8String.IsNullOrEmpty(type.Name)); + } +} From 3766e240068b1e22a6d61ed5eeb97068f4328ae6 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 30 Jul 2022 14:54:08 +0200 Subject: [PATCH 073/182] Add read support LF_PROCEDURE records. --- .../Leaves/CodeViewLeaf.cs | 1 + .../Leaves/ProcedureType.cs | 101 ++++++++++++++++++ .../Serialized/SerializedProcedureType.cs | 52 +++++++++ .../Leaves/ProcedureTypeTest.cs | 36 +++++++ 4 files changed, 190 insertions(+) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/ProcedureType.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureType.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 53e915985..b51a58d2c 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -68,6 +68,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex NestType or NestTypeEx => new SerializedNestedType(context, typeIndex, ref dataReader), OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader), Pointer => new SerializedPointerType(context, typeIndex, dataReader), + Procedure => new SerializedProcedureType(context, typeIndex, dataReader), Structure => new SerializedClassType(Structure, context, typeIndex, dataReader), Union => new SerializedUnionType(context, typeIndex, dataReader), VTShape => new SerializedVTableShape(context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureType.cs new file mode 100644 index 000000000..cfdac7135 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureType.cs @@ -0,0 +1,101 @@ +using System.Linq; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a function pointer or procedure type. +/// +public class ProcedureType : CodeViewType +{ + private readonly LazyVariable _returnType; + private readonly LazyVariable _argumentList; + + /// + /// Initializes an empty procedure type. + /// + /// The type index to assign to the type. + protected ProcedureType(uint typeIndex) + : base(typeIndex) + { + _returnType = new LazyVariable(GetReturnType); + _argumentList = new LazyVariable(GetArguments); + } + + /// + /// Creates a new procedure type. + /// + /// The convention to use when calling the function pointed by values of this type. + /// The return type of the function. + /// The argument type list of the function. + public ProcedureType(CodeViewCallingConvention callingConvention, CodeViewType returnType, ArgumentList arguments) + : base(0) + { + CallingConvention = callingConvention; + _returnType = new LazyVariable(returnType); + _argumentList = new LazyVariable(arguments); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Procedure; + + /// + /// Gets or sets the return type of the function. + /// + public CodeViewType? ReturnType + { + get => _returnType.Value; + set => _returnType.Value = value; + } + + /// + /// Gets or sets the convention that is used when calling the member function. + /// + public CodeViewCallingConvention CallingConvention + { + get; + set; + } + + /// + /// Gets or sets the attributes associated to the function. + /// + public MemberFunctionAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the list of types of the parameters that this function defines. + /// + public ArgumentList? Arguments + { + get => _argumentList.Value; + set => _argumentList.Value = value; + } + + /// + /// Obtains the return type of the procedure. + /// + /// The return type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewType? GetReturnType() => null; + + /// + /// Obtains the argument types of the procedure.. + /// + /// The argument types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ArgumentList? GetArguments() => null; + + /// + public override string ToString() + { + string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); + return $"{CallingConvention} {ReturnType} *({args})"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureType.cs new file mode 100644 index 000000000..13ec71139 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureType.cs @@ -0,0 +1,52 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedProcedureType : ProcedureType +{ + private readonly PdbReaderContext _context; + private readonly uint _returnTypeIndex; + private readonly ushort _parameterCount; + private readonly uint _argumentListIndex; + + /// + /// Reads a procedure type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedProcedureType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _returnTypeIndex = reader.ReadUInt32(); + CallingConvention = (CodeViewCallingConvention) reader.ReadByte(); + Attributes = (MemberFunctionAttributes) reader.ReadByte(); + _parameterCount = reader.ReadUInt16(); + _argumentListIndex = reader.ReadUInt32(); + } + + /// + protected override CodeViewType? GetReturnType() + { + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewType type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Procedure type {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); + } + + /// + protected override ArgumentList? GetArguments() + { + if (_argumentListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentList list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Procedure type {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}."); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeTest.cs new file mode 100644 index 000000000..f22798f28 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeTest.cs @@ -0,0 +1,36 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ProcedureTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ProcedureTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadReturnType() + { + var procedure = (ProcedureType) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(procedure.ReturnType).Kind); + } + + [Fact] + public void ReadCallingConvention() + { + var procedure = (ProcedureType) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.Equal(CodeViewCallingConvention.NearStd, procedure.CallingConvention); + } + + [Fact] + public void ReadArguments() + { + var procedure = (ProcedureType) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.NotNull(procedure.Arguments); + Assert.Equal(2, procedure.Arguments!.Types.Count); + } +} From 86bffa08f39645c4ae5c10f7b52574e9b3158429 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 30 Jul 2022 15:24:05 +0200 Subject: [PATCH 074/182] Refactor by adding Leaf or TypeRecord suffixes to CV leaves. --- .../{ArgumentList.cs => ArgumentListLeaf.cs} | 19 ++++---- .../{ArrayType.cs => ArrayTypeRecord.cs} | 32 ++++++------- .../{BaseClass.cs => BaseClassField.cs} | 12 ++--- .../{ClassType.cs => ClassTypeRecord.cs} | 18 +++---- ...Type.cs => CodeViewCompositeTypeRecord.cs} | 12 ++--- ...edType.cs => CodeViewDerivedTypeRecord.cs} | 12 ++--- .../Leaves/CodeViewLeaf.cs | 34 +++++++------- ...{CodeViewType.cs => CodeViewTypeRecord.cs} | 4 +- .../Leaves/{EnumType.cs => EnumTypeRecord.cs} | 6 +-- .../Leaves/{FieldList.cs => FieldListLeaf.cs} | 6 +-- ...anceDataMember.cs => InstanceDataField.cs} | 16 +++---- ...emberFunction.cs => MemberFunctionLeaf.cs} | 38 +++++++-------- .../Leaves/MethodListEntry.cs | 16 +++---- .../{MethodList.cs => MethodListLeaf.cs} | 6 +-- ...{ModifierType.cs => ModifierTypeRecord.cs} | 16 +++---- .../{NestedType.cs => NestedTypeField.cs} | 20 ++++---- .../Leaves/NonOverloadedMethod.cs | 16 +++---- .../Leaves/OverloadedMethod.cs | 8 ++-- .../{PointerType.cs => PointerTypeRecord.cs} | 20 ++++---- ...rocedureType.cs => ProcedureTypeRecord.cs} | 28 +++++------ ...tList.cs => SerializedArgumentListLeaf.cs} | 12 ++--- ...ayType.cs => SerializedArrayTypeRecord.cs} | 18 +++---- ...seClass.cs => SerializedBaseClassField.cs} | 12 ++--- ...ssType.cs => SerializedClassTypeRecord.cs} | 24 +++++----- ...numType.cs => SerializedEnumTypeRecord.cs} | 18 +++---- ...ieldList.cs => SerializedFieldListLeaf.cs} | 6 +-- ...mber.cs => SerializedInstanceDataField.cs} | 12 ++--- ...ion.cs => SerializedMemberFunctionLeaf.cs} | 30 ++++++------ .../Serialized/SerializedMethodListEntry.cs | 6 +-- ...hodList.cs => SerializedMethodListLeaf.cs} | 6 +-- ...ype.cs => SerializedModifierTypeRecord.cs} | 12 ++--- ...edType.cs => SerializedNestedTypeField.cs} | 12 ++--- .../Serialized/SerializedOverloadedMethod.cs | 6 +-- ...Type.cs => SerializedPointerTypeRecord.cs} | 12 ++--- ...pe.cs => SerializedProcedureTypeRecord.cs} | 18 +++---- ...onType.cs => SerializedUnionTypeRecord.cs} | 12 ++--- ...eShape.cs => SerializedVTableShapeLeaf.cs} | 6 +-- .../{SimpleType.cs => SimpleTypeRecord.cs} | 14 ++++-- .../{UnionType.cs => UnionTypeRecord.cs} | 17 +++++-- .../Leaves/VTableShapeEntry.cs | 2 +- .../{VTableShape.cs => VTableShapeLeaf.cs} | 8 ++-- src/AsmResolver.Symbols.Pdb/PdbImage.cs | 4 +- .../Records/ConstantSymbol.cs | 12 ++--- .../Serialized/SerializedConstantSymbol.cs | 6 +-- .../SerializedUserDefinedTypeSymbol.cs | 6 +-- .../Records/UserDefinedTypeSymbol.cs | 12 ++--- .../Leaves/ArgumentListLeafTest.cs | 23 +++++++++ .../Leaves/ArgumentListTest.cs | 23 --------- ...rrayTypeTest.cs => ArrayTypeRecordTest.cs} | 16 +++---- ...lassTypeTest.cs => ClassTypeRecordTest.cs} | 12 ++--- ...{EnumTypeTest.cs => EnumTypeRecordTest.cs} | 6 +-- ...{FieldListTest.cs => FieldListLeafTest.cs} | 22 ++++----- .../Leaves/FunctionTest.cs | 44 ----------------- .../Leaves/MemberFunctionLeafTest.cs | 44 +++++++++++++++++ ...ethodListTest.cs => MethodListLeafTest.cs} | 6 +-- ...rTypeTest.cs => ModifierTypeRecordTest.cs} | 10 ++-- ...erTypeTest.cs => PointerTypeRecordTest.cs} | 16 +++---- ...TypeTest.cs => ProcedureTypeRecordTest.cs} | 12 ++--- ...pleTypeTest.cs => SimpleTypeRecordTest.cs} | 4 +- .../Leaves/UnionTypeRecordTest.cs | 47 +++++++++++++++++++ .../Leaves/UnionTypeTest.cs | 47 ------------------- ...bleShapeTest.cs => VTableShapeLeafTest.cs} | 6 +-- .../PdbImageTest.cs | 2 +- .../Records/UserDefinedTypeTest.cs | 2 +- 64 files changed, 500 insertions(+), 484 deletions(-) rename src/AsmResolver.Symbols.Pdb/Leaves/{ArgumentList.cs => ArgumentListLeaf.cs} (70%) rename src/AsmResolver.Symbols.Pdb/Leaves/{ArrayType.cs => ArrayTypeRecord.cs} (73%) rename src/AsmResolver.Symbols.Pdb/Leaves/{BaseClass.cs => BaseClassField.cs} (78%) rename src/AsmResolver.Symbols.Pdb/Leaves/{ClassType.cs => ClassTypeRecord.cs} (83%) rename src/AsmResolver.Symbols.Pdb/Leaves/{CodeViewCompositeType.cs => CodeViewCompositeTypeRecord.cs} (82%) rename src/AsmResolver.Symbols.Pdb/Leaves/{CodeViewDerivedType.cs => CodeViewDerivedTypeRecord.cs} (66%) rename src/AsmResolver.Symbols.Pdb/Leaves/{CodeViewType.cs => CodeViewTypeRecord.cs} (76%) rename src/AsmResolver.Symbols.Pdb/Leaves/{EnumType.cs => EnumTypeRecord.cs} (79%) rename src/AsmResolver.Symbols.Pdb/Leaves/{FieldList.cs => FieldListLeaf.cs} (91%) rename src/AsmResolver.Symbols.Pdb/Leaves/{InstanceDataMember.cs => InstanceDataField.cs} (76%) rename src/AsmResolver.Symbols.Pdb/Leaves/{MemberFunction.cs => MemberFunctionLeaf.cs} (73%) rename src/AsmResolver.Symbols.Pdb/Leaves/{MethodList.cs => MethodListLeaf.cs} (91%) rename src/AsmResolver.Symbols.Pdb/Leaves/{ModifierType.cs => ModifierTypeRecord.cs} (83%) rename src/AsmResolver.Symbols.Pdb/Leaves/{NestedType.cs => NestedTypeField.cs} (72%) rename src/AsmResolver.Symbols.Pdb/Leaves/{PointerType.cs => PointerTypeRecord.cs} (95%) rename src/AsmResolver.Symbols.Pdb/Leaves/{ProcedureType.cs => ProcedureTypeRecord.cs} (72%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedArgumentList.cs => SerializedArgumentListLeaf.cs} (76%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedArrayType.cs => SerializedArrayTypeRecord.cs} (77%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedBaseClass.cs => SerializedBaseClassField.cs} (76%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedClassType.cs => SerializedClassTypeRecord.cs} (81%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedEnumType.cs => SerializedEnumTypeRecord.cs} (79%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedFieldList.cs => SerializedFieldListLeaf.cs} (89%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedInstanceDataMember.cs => SerializedInstanceDataField.cs} (81%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedMemberFunction.cs => SerializedMemberFunctionLeaf.cs} (76%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedMethodList.cs => SerializedMethodListLeaf.cs} (83%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedModifierType.cs => SerializedModifierTypeRecord.cs} (74%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedNestedType.cs => SerializedNestedTypeField.cs} (78%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedPointerType.cs => SerializedPointerTypeRecord.cs} (75%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedProcedureType.cs => SerializedProcedureTypeRecord.cs} (77%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedUnionType.cs => SerializedUnionTypeRecord.cs} (83%) rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedVTableShape.cs => SerializedVTableShapeLeaf.cs} (86%) rename src/AsmResolver.Symbols.Pdb/Leaves/{SimpleType.cs => SimpleTypeRecord.cs} (81%) rename src/AsmResolver.Symbols.Pdb/Leaves/{UnionType.cs => UnionTypeRecord.cs} (79%) rename src/AsmResolver.Symbols.Pdb/Leaves/{VTableShape.cs => VTableShapeLeaf.cs} (89%) create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs delete mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListTest.cs rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{ArrayTypeTest.cs => ArrayTypeRecordTest.cs} (54%) rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{ClassTypeTest.cs => ClassTypeRecordTest.cs} (76%) rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{EnumTypeTest.cs => EnumTypeRecordTest.cs} (81%) rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{FieldListTest.cs => FieldListLeafTest.cs} (79%) delete mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionTest.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{MethodListTest.cs => MethodListLeafTest.cs} (76%) rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{ModifierTypeTest.cs => ModifierTypeRecordTest.cs} (55%) rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{PointerTypeTest.cs => PointerTypeRecordTest.cs} (56%) rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{ProcedureTypeTest.cs => ProcedureTypeRecordTest.cs} (58%) rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{SimpleTypeTest.cs => SimpleTypeRecordTest.cs} (83%) create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs delete mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeTest.cs rename test/AsmResolver.Symbols.Pdb.Tests/Leaves/{VTableShapeTest.cs => VTableShapeLeafTest.cs} (70%) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs similarity index 70% rename from src/AsmResolver.Symbols.Pdb/Leaves/ArgumentList.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs index ffa74b8cd..32e5bccc7 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs @@ -6,15 +6,15 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a leaf containing a list of type arguments for a function or method. /// -public class ArgumentList : CodeViewLeaf +public class ArgumentListLeaf : CodeViewLeaf { - private IList? _types; + private IList? _types; /// /// Initializes an empty argument list. /// /// The type index to assign to the list. - protected ArgumentList(uint typeIndex) + protected ArgumentListLeaf(uint typeIndex) : base(typeIndex) { } @@ -22,7 +22,7 @@ protected ArgumentList(uint typeIndex) /// /// Creates a new empty argument list. /// - public ArgumentList() + public ArgumentListLeaf() : base(0) { } @@ -30,10 +30,10 @@ public ArgumentList() /// /// Creates a new argument list. /// - public ArgumentList(params CodeViewType[] argumentTypes) + public ArgumentListLeaf(params CodeViewTypeRecord[] argumentTypes) : base(0) { - _types = new List(argumentTypes); + _types = new List(argumentTypes); } /// @@ -42,7 +42,7 @@ public ArgumentList(params CodeViewType[] argumentTypes) /// /// Gets an ordered collection of types that correspond to the types of each parameter. /// - public IList Types + public IList Types { get { @@ -59,5 +59,8 @@ public IList Types /// /// This method is called upon initialization of the property. /// - protected virtual IList GetArgumentTypes() => new List(); + protected virtual IList GetArgumentTypes() => new List(); + + /// + public override string ToString() => string.Join(", ", Types); } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArrayType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs similarity index 73% rename from src/AsmResolver.Symbols.Pdb/Leaves/ArrayType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs index 9fd14ec62..1f53f2071 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ArrayType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs @@ -3,21 +3,21 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a type describing an array of elements. /// -public class ArrayType : CodeViewType +public class ArrayTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _elementType; - private readonly LazyVariable _indexType; + private readonly LazyVariable _elementType; + private readonly LazyVariable _indexType; private readonly LazyVariable _name; /// /// Initializes a new empty array type. /// /// The type index to assign to the type. - protected ArrayType(uint typeIndex) + protected ArrayTypeRecord(uint typeIndex) : base(typeIndex) { - _elementType = new LazyVariable(GetElementType); - _indexType = new LazyVariable(GetIndexType); + _elementType = new LazyVariable(GetElementType); + _indexType = new LazyVariable(GetIndexType); _name = new LazyVariable(GetName); } @@ -27,11 +27,11 @@ protected ArrayType(uint typeIndex) /// The type of each element in the array. /// The type to use for indexing into the array. /// The number of elements in the array. - public ArrayType(CodeViewType elementType, CodeViewType indexType, ulong length) + public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length) : base(0) { - _elementType = new LazyVariable(elementType); - _indexType = new LazyVariable(indexType); + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); Length = length; _name = new LazyVariable(Utf8String.Empty); } @@ -43,11 +43,11 @@ public ArrayType(CodeViewType elementType, CodeViewType indexType, ulong length) /// The type to use for indexing into the array. /// The number of elements in the array. /// The name of the array type. - public ArrayType(CodeViewType elementType, CodeViewType indexType, ulong length, Utf8String name) + public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length, Utf8String name) : base(0) { - _elementType = new LazyVariable(elementType); - _indexType = new LazyVariable(indexType); + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); Length = length; _name = new LazyVariable(Name); } @@ -58,7 +58,7 @@ public ArrayType(CodeViewType elementType, CodeViewType indexType, ulong length, /// /// Gets or sets the type of each element in the array. /// - public CodeViewType? ElementType + public CodeViewTypeRecord? ElementType { get => _elementType.Value; set => _elementType.Value = value; @@ -67,7 +67,7 @@ public ArrayType(CodeViewType elementType, CodeViewType indexType, ulong length, /// /// Gets or sets the type that is used to index into the array. /// - public CodeViewType? IndexType + public CodeViewTypeRecord? IndexType { get => _indexType.Value; set => _indexType.Value = value; @@ -98,7 +98,7 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetElementType() => null; + protected virtual CodeViewTypeRecord? GetElementType() => null; /// /// Obtains the index type of the array. @@ -107,7 +107,7 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetIndexType() => null; + protected virtual CodeViewTypeRecord? GetIndexType() => null; /// /// Obtains the name type of the array. diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClass.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs similarity index 78% rename from src/AsmResolver.Symbols.Pdb/Leaves/BaseClass.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs index 028e1d244..355a5be80 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClass.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs @@ -3,18 +3,18 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a reference to a base class object in a structure. /// -public class BaseClass : CodeViewField +public class BaseClassField : CodeViewField { - private readonly LazyVariable _type; + private readonly LazyVariable _type; /// /// Initializes an empty base class. /// /// The type index to assign to the base class field. - protected BaseClass(uint typeIndex) + protected BaseClassField(uint typeIndex) : base(typeIndex) { - _type = new LazyVariable(GetBaseType); + _type = new LazyVariable(GetBaseType); } /// @@ -23,7 +23,7 @@ protected BaseClass(uint typeIndex) /// /// Gets or sets the base type that this base class is referencing. /// - public CodeViewType? Type + public CodeViewTypeRecord? Type { get => _type.Value; set => _type.Value = value; @@ -45,7 +45,7 @@ public ulong Offset /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetBaseType() => null; + protected virtual CodeViewTypeRecord? GetBaseType() => null; /// public override string ToString() => Type?.ToString() ?? "<<>>"; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs similarity index 83% rename from src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs index 3eac1e127..3f222effc 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ClassType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs @@ -5,10 +5,10 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a class, structure or interface type in a PDB. /// -public class ClassType : CodeViewDerivedType +public class ClassTypeRecord : CodeViewDerivedTypeRecord { private readonly LazyVariable _uniqueName; - private readonly LazyVariable _vtableShape; + private readonly LazyVariable _vtableShape; /// /// Initializes an empty class type. @@ -18,7 +18,7 @@ public class ClassType : CodeViewDerivedType /// /// Occurs when the provided kind is not a class, structure or interface. /// - protected ClassType(CodeViewLeafKind kind, uint typeIndex) + protected ClassTypeRecord(CodeViewLeafKind kind, uint typeIndex) : base(typeIndex) { if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface)) @@ -26,7 +26,7 @@ protected ClassType(CodeViewLeafKind kind, uint typeIndex) LeafKind = kind; _uniqueName = new LazyVariable(GetUniqueName); - _vtableShape = new LazyVariable(GetVTableShape); + _vtableShape = new LazyVariable(GetVTableShape); } /// @@ -41,8 +41,8 @@ protected ClassType(CodeViewLeafKind kind, uint typeIndex) /// /// Occurs when the provided kind is not a class, structure or interface. /// - public ClassType(CodeViewLeafKind kind, Utf8String name, Utf8String uniqueName, ulong size, - StructureAttributes attributes, CodeViewType? baseType) + public ClassTypeRecord(CodeViewLeafKind kind, Utf8String name, Utf8String uniqueName, ulong size, + StructureAttributes attributes, CodeViewTypeRecord? baseType) : base(0) { if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface)) @@ -51,7 +51,7 @@ protected ClassType(CodeViewLeafKind kind, uint typeIndex) LeafKind = kind; Name = name; _uniqueName = new LazyVariable(uniqueName); - _vtableShape = new LazyVariable(default(VTableShape)); + _vtableShape = new LazyVariable(default(VTableShapeLeaf)); Size = size; StructureAttributes = attributes; BaseType = baseType; @@ -84,7 +84,7 @@ public Utf8String UniqueName /// /// Gets or sets the shape of the virtual function table of this type, if available. /// - public VTableShape? VTableShape + public VTableShapeLeaf? VTableShape { get => _vtableShape.Value; set => _vtableShape.Value = value; @@ -106,5 +106,5 @@ public Utf8String UniqueName /// /// This method is called upon initialization of the property. /// - protected virtual VTableShape? GetVTableShape() => null; + protected virtual VTableShapeLeaf? GetVTableShape() => null; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs similarity index 82% rename from src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs index d499fa06e..9abc46710 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs @@ -3,20 +3,20 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Provides a base for all code view types that can define one or more fields. /// -public abstract class CodeViewCompositeType : CodeViewType +public abstract class CodeViewCompositeTypeRecord : CodeViewTypeRecord { private readonly LazyVariable _name; - private readonly LazyVariable _fields; + private readonly LazyVariable _fields; /// /// Initializes a new empty composite type. /// /// The type index to assign to the type. - protected CodeViewCompositeType(uint typeIndex) + protected CodeViewCompositeTypeRecord(uint typeIndex) : base(typeIndex) { _name = new LazyVariable(GetName); - _fields = new LazyVariable(GetFields); + _fields = new LazyVariable(GetFields); } /// @@ -40,7 +40,7 @@ public Utf8String Name /// /// Gets a collection of fields that are defined in the enum. /// - public FieldList? Fields + public FieldListLeaf? Fields { get => _fields.Value; set => _fields.Value = value; @@ -62,5 +62,5 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual FieldList? GetFields() => null; + protected virtual FieldListLeaf? GetFields() => null; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs similarity index 66% rename from src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs index f73a8ce21..3cd51d0f8 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs @@ -3,21 +3,21 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Provides a base for all custom types that may be derived from a base type. /// -public abstract class CodeViewDerivedType : CodeViewCompositeType +public abstract class CodeViewDerivedTypeRecord : CodeViewCompositeTypeRecord { - private readonly LazyVariable _baseType; + private readonly LazyVariable _baseType; /// - protected CodeViewDerivedType(uint typeIndex) + protected CodeViewDerivedTypeRecord(uint typeIndex) : base(typeIndex) { - _baseType = new LazyVariable(GetBaseType); + _baseType = new LazyVariable(GetBaseType); } /// /// Gets or sets the base type that this type is deriving from. /// - public CodeViewType? BaseType + public CodeViewTypeRecord? BaseType { get => _baseType.Value; set => _baseType.Value = value; @@ -30,7 +30,7 @@ protected CodeViewDerivedType(uint typeIndex) /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetBaseType() => null; + protected virtual CodeViewTypeRecord? GetBaseType() => null; /// public override string ToString() => Name; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index b51a58d2c..5dece241a 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -52,26 +52,26 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); return kind switch { - Array => new SerializedArrayType(context, typeIndex, dataReader), - ArgList => new SerializedArgumentList(context, typeIndex, dataReader), - BClass => new SerializedBaseClass(context, typeIndex, ref dataReader), - Class => new SerializedClassType(Class, context, typeIndex, dataReader), - Enum => new SerializedEnumType(context, typeIndex, dataReader), + Array => new SerializedArrayTypeRecord(context, typeIndex, dataReader), + ArgList => new SerializedArgumentListLeaf(context, typeIndex, dataReader), + BClass => new SerializedBaseClassField(context, typeIndex, ref dataReader), + Class => new SerializedClassTypeRecord(Class, context, typeIndex, dataReader), + Enum => new SerializedEnumTypeRecord(context, typeIndex, dataReader), Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), - CodeViewLeafKind.FieldList => new SerializedFieldList(context, typeIndex, dataReader), - Interface => new SerializedClassType(Interface, context, typeIndex, dataReader), - Member => new SerializedInstanceDataMember(context, typeIndex, ref dataReader), + CodeViewLeafKind.FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader), + Interface => new SerializedClassTypeRecord(Interface, context, typeIndex, dataReader), + Member => new SerializedInstanceDataField(context, typeIndex, ref dataReader), Method => new SerializedOverloadedMethod(context, typeIndex, ref dataReader), - CodeViewLeafKind.MethodList => new SerializedMethodList(context, typeIndex, dataReader), - MFunction => new SerializedMemberFunction(context, typeIndex, dataReader), - Modifier => new SerializedModifierType(context, typeIndex, dataReader), - NestType or NestTypeEx => new SerializedNestedType(context, typeIndex, ref dataReader), + CodeViewLeafKind.MethodList => new SerializedMethodListLeaf(context, typeIndex, dataReader), + MFunction => new SerializedMemberFunctionLeaf(context, typeIndex, dataReader), + Modifier => new SerializedModifierTypeRecord(context, typeIndex, dataReader), + NestType or NestTypeEx => new SerializedNestedTypeField(context, typeIndex, ref dataReader), OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader), - Pointer => new SerializedPointerType(context, typeIndex, dataReader), - Procedure => new SerializedProcedureType(context, typeIndex, dataReader), - Structure => new SerializedClassType(Structure, context, typeIndex, dataReader), - Union => new SerializedUnionType(context, typeIndex, dataReader), - VTShape => new SerializedVTableShape(context, typeIndex, dataReader), + Pointer => new SerializedPointerTypeRecord(context, typeIndex, dataReader), + Procedure => new SerializedProcedureTypeRecord(context, typeIndex, dataReader), + Structure => new SerializedClassTypeRecord(Structure, context, typeIndex, dataReader), + Union => new SerializedUnionTypeRecord(context, typeIndex, dataReader), + VTShape => new SerializedVTableShapeLeaf(context, typeIndex, dataReader), _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs similarity index 76% rename from src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs index 1943e4b31..097f66be0 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs @@ -3,13 +3,13 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a single type record in a TPI or IPI stream. /// -public abstract class CodeViewType : CodeViewLeaf +public abstract class CodeViewTypeRecord : CodeViewLeaf { /// /// Initializes an empty CodeView type record. /// /// The type index to assign to the leaf. - protected CodeViewType(uint typeIndex) + protected CodeViewTypeRecord(uint typeIndex) : base(typeIndex) { } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs similarity index 79% rename from src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs index f2b677155..a88bc9c73 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs @@ -3,13 +3,13 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents an enum type. /// -public class EnumType : CodeViewDerivedType +public class EnumTypeRecord : CodeViewDerivedTypeRecord { /// /// Initializes a new empty enum type. /// /// The type index to assign to the enum type. - protected EnumType(uint typeIndex) + protected EnumTypeRecord(uint typeIndex) : base(typeIndex) { } @@ -20,7 +20,7 @@ protected EnumType(uint typeIndex) /// The name of the enum. /// The underlying type of all members in the enum. /// The structural attributes assigned to the enum. - public EnumType(Utf8String name, CodeViewType underlyingType, StructureAttributes attributes) + public EnumTypeRecord(Utf8String name, CodeViewTypeRecord underlyingType, StructureAttributes attributes) : base(0) { Name = name; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs similarity index 91% rename from src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs index 72e596542..5eecc0abe 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/FieldList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs @@ -6,7 +6,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a leaf containing a list of fields. /// -public class FieldList : CodeViewLeaf +public class FieldListLeaf : CodeViewLeaf { private IList? _fields; @@ -14,7 +14,7 @@ public class FieldList : CodeViewLeaf /// Initializes an empty field list. /// /// The type index to assign to the list. - protected FieldList(uint typeIndex) + protected FieldListLeaf(uint typeIndex) : base(typeIndex) { } @@ -22,7 +22,7 @@ protected FieldList(uint typeIndex) /// /// Creates a new empty field list. /// - public FieldList() + public FieldListLeaf() : base(0) { } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs similarity index 76% rename from src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs index e87a7f210..39ecd208e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataMember.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs @@ -3,18 +3,18 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents an instance data member in a class or structure type. /// -public class InstanceDataMember : CodeViewField +public class InstanceDataField : CodeViewField { - private readonly LazyVariable _dataType; + private readonly LazyVariable _dataType; /// /// Initializes an empty instance data member. /// /// The type index to assign to the member. - protected InstanceDataMember(uint typeIndex) + protected InstanceDataField(uint typeIndex) : base(typeIndex) { - _dataType = new LazyVariable(GetDataType); + _dataType = new LazyVariable(GetDataType); } /// @@ -23,10 +23,10 @@ protected InstanceDataMember(uint typeIndex) /// The data type of the member. /// The name of the member. /// The byte offset within the class or structure that the member is stored at. - public InstanceDataMember(CodeViewType dataType, Utf8String name, ulong offset) + public InstanceDataField(CodeViewTypeRecord dataType, Utf8String name, ulong offset) : base(0) { - _dataType = new LazyVariable(dataType); + _dataType = new LazyVariable(dataType); Name = name; Offset = offset; } @@ -37,7 +37,7 @@ public InstanceDataMember(CodeViewType dataType, Utf8String name, ulong offset) /// /// Gets or sets the data type of the member. /// - public CodeViewType DataType + public CodeViewTypeRecord DataType { get => _dataType.Value; set => _dataType.Value = value; @@ -59,7 +59,7 @@ public ulong Offset /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetDataType() => null; + protected virtual CodeViewTypeRecord? GetDataType() => null; /// public override string ToString() => $"[{Offset:X4}]: {DataType} {Name}"; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunction.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs similarity index 73% rename from src/AsmResolver.Symbols.Pdb/Leaves/MemberFunction.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs index 4d5724678..520747643 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunction.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs @@ -5,24 +5,24 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a single instance member function. /// -public class MemberFunction : CodeViewLeaf +public class MemberFunctionLeaf : CodeViewLeaf { - private readonly LazyVariable _returnType; - private readonly LazyVariable _declaringType; - private readonly LazyVariable _thisType; - private readonly LazyVariable _argumentList; + private readonly LazyVariable _returnType; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _thisType; + private readonly LazyVariable _argumentList; /// /// Initializes an empty member function. /// /// The type index to assign to the function. - protected MemberFunction(uint typeIndex) + protected MemberFunctionLeaf(uint typeIndex) : base(typeIndex) { - _returnType = new LazyVariable(GetReturnType); - _declaringType = new LazyVariable(GetDeclaringType); - _thisType = new LazyVariable(GetThisType); - _argumentList = new LazyVariable(GetArguments); + _returnType = new LazyVariable(GetReturnType); + _declaringType = new LazyVariable(GetDeclaringType); + _thisType = new LazyVariable(GetThisType); + _argumentList = new LazyVariable(GetArguments); } /// @@ -31,7 +31,7 @@ protected MemberFunction(uint typeIndex) /// /// Gets or sets the return type of the function. /// - public CodeViewType? ReturnType + public CodeViewTypeRecord? ReturnType { get => _returnType.Value; set => _returnType.Value = value; @@ -40,7 +40,7 @@ protected MemberFunction(uint typeIndex) /// /// Gets or sets the type that declares this member function. /// - public CodeViewType? DeclaringType + public CodeViewTypeRecord? DeclaringType { get => _declaringType.Value; set => _declaringType.Value = value; @@ -49,7 +49,7 @@ protected MemberFunction(uint typeIndex) /// /// Gets or sets the type of the this pointer that is used to access the member function. /// - public CodeViewType? ThisType + public CodeViewTypeRecord? ThisType { get => _thisType.Value; set => _thisType.Value = value; @@ -76,7 +76,7 @@ public MemberFunctionAttributes Attributes /// /// Gets or sets the list of types of the parameters that this function defines. /// - public ArgumentList? Arguments + public ArgumentListLeaf? Arguments { get => _argumentList.Value; set => _argumentList.Value = value; @@ -98,7 +98,7 @@ public uint ThisAdjuster /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetReturnType() => null; + protected virtual CodeViewTypeRecord? GetReturnType() => null; /// /// Obtains the declaring type of the function. @@ -107,7 +107,7 @@ public uint ThisAdjuster /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetDeclaringType() => null; + protected virtual CodeViewTypeRecord? GetDeclaringType() => null; /// /// Obtains the this-type of the function. @@ -116,7 +116,7 @@ public uint ThisAdjuster /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetThisType() => null; + protected virtual CodeViewTypeRecord? GetThisType() => null; /// /// Obtains the argument types of the function. @@ -125,12 +125,12 @@ public uint ThisAdjuster /// /// This method is called upon initialization of the property. /// - protected virtual ArgumentList? GetArguments() => null; + protected virtual ArgumentListLeaf? GetArguments() => null; /// public override string ToString() { - string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); + string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); return $"{CallingConvention} {ReturnType} {DeclaringType}::*({args})"; } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs index 61bc5bb76..73fbc906e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs @@ -5,14 +5,14 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class MethodListEntry { - private readonly LazyVariable _function; + private readonly LazyVariable _function; /// /// Initializes an empty method list entry. /// protected MethodListEntry() { - _function = new LazyVariable(GetFunction); + _function = new LazyVariable(GetFunction); } /// @@ -20,10 +20,10 @@ protected MethodListEntry() /// /// The attributes associated to this method. /// The referenced function. - public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunction function) + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function) { Attributes = attributes; - _function = new LazyVariable(function); + _function = new LazyVariable(function); VFTableOffset = 0; } @@ -33,10 +33,10 @@ public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunction functi /// The attributes associated to this method. /// The referenced function. /// The offset to the slot the virtual function table that this method occupies. - public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunction function, uint vfTableOffset) + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function, uint vfTableOffset) { Attributes = attributes; - _function = new LazyVariable(function); + _function = new LazyVariable(function); VFTableOffset = vfTableOffset; } @@ -52,7 +52,7 @@ public CodeViewFieldAttributes Attributes /// /// Gets or sets the function that is referenced by this method. /// - public MemberFunction? Function + public MemberFunctionLeaf? Function { get => _function.Value; set => _function.Value = value; @@ -82,7 +82,7 @@ public uint VFTableOffset /// /// This method is called upon initialization of the property. /// - protected virtual MemberFunction? GetFunction() => null; + protected virtual MemberFunctionLeaf? GetFunction() => null; /// public override string ToString() diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs similarity index 91% rename from src/AsmResolver.Symbols.Pdb/Leaves/MethodList.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs index 83838059f..77756447d 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MethodList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs @@ -6,7 +6,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a leaf record containing a list of overloaded methods. /// -public class MethodList : CodeViewLeaf +public class MethodListLeaf : CodeViewLeaf { private IList? _entries; @@ -14,7 +14,7 @@ public class MethodList : CodeViewLeaf /// Initializes an empty method list. /// /// The type index to assign to the list. - protected MethodList(uint typeIndex) + protected MethodListLeaf(uint typeIndex) : base(typeIndex) { } @@ -22,7 +22,7 @@ protected MethodList(uint typeIndex) /// /// Creates a new empty method list. /// - public MethodList() + public MethodListLeaf() : base(0) { } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs similarity index 83% rename from src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs index 6ac452c4a..c70e50ed1 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs @@ -3,18 +3,18 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a type that is annotated with extra modifiers. /// -public class ModifierType : CodeViewType +public class ModifierTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _baseType; + private readonly LazyVariable _baseType; /// /// Initializes a new empty modifier type. /// /// The type index to assign to the modifier type. - protected ModifierType(uint typeIndex) + protected ModifierTypeRecord(uint typeIndex) : base(typeIndex) { - _baseType = new LazyVariable(GetBaseType); + _baseType = new LazyVariable(GetBaseType); } /// @@ -22,10 +22,10 @@ protected ModifierType(uint typeIndex) /// /// The type to be modified. /// The attributes describing the shape of the pointer. - public ModifierType(CodeViewType type, ModifierAttributes attributes) + public ModifierTypeRecord(CodeViewTypeRecord type, ModifierAttributes attributes) : base(0) { - _baseType = new LazyVariable(type); + _baseType = new LazyVariable(type); Attributes = attributes; } @@ -35,7 +35,7 @@ public ModifierType(CodeViewType type, ModifierAttributes attributes) /// /// Gets or sets the type that is annotated. /// - public CodeViewType BaseType + public CodeViewTypeRecord BaseType { get => _baseType.Value; set => _baseType.Value = value; @@ -87,7 +87,7 @@ public bool IsUnaligned /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetBaseType() => null; + protected virtual CodeViewTypeRecord? GetBaseType() => null; /// public override string ToString() => $"{BaseType} {Attributes}"; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NestedType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs similarity index 72% rename from src/AsmResolver.Symbols.Pdb/Leaves/NestedType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs index 33a1ecb02..0f6236113 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/NestedType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs @@ -3,18 +3,18 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a field in a type that references a nested type definition. /// -public class NestedType : CodeViewField +public class NestedTypeField : CodeViewField { - private readonly LazyVariable _type; + private readonly LazyVariable _type; /// /// Initializes an empty nested type. /// /// The type index to assign to the nested type field. - protected NestedType(uint typeIndex) + protected NestedTypeField(uint typeIndex) : base(typeIndex) { - _type = new LazyVariable(GetNestedType); + _type = new LazyVariable(GetNestedType); } /// @@ -22,10 +22,10 @@ protected NestedType(uint typeIndex) /// /// The definition of the nested type /// The name of the nested type. - public NestedType(CodeViewType type, Utf8String name) + public NestedTypeField(CodeViewTypeRecord type, Utf8String name) : base(0) { - _type = new LazyVariable(type); + _type = new LazyVariable(type); Name = name; Attributes = 0; } @@ -36,10 +36,10 @@ public NestedType(CodeViewType type, Utf8String name) /// The definition of the nested type /// The name of the nested type. /// The attributes assigned to the type. - public NestedType(CodeViewType type, Utf8String name, CodeViewFieldAttributes attributes) + public NestedTypeField(CodeViewTypeRecord type, Utf8String name, CodeViewFieldAttributes attributes) : base(0) { - _type = new LazyVariable(type); + _type = new LazyVariable(type); Name = name; Attributes = attributes; } @@ -52,7 +52,7 @@ public NestedType(CodeViewType type, Utf8String name, CodeViewFieldAttributes at /// /// Gets or sets the definition of the referenced nested type. /// - public CodeViewType? Type + public CodeViewTypeRecord? Type { get => _type.Value; set => _type.Value = value; @@ -65,5 +65,5 @@ public NestedType(CodeViewType type, Utf8String name, CodeViewFieldAttributes at /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetNestedType() => null; + protected virtual CodeViewTypeRecord? GetNestedType() => null; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs index 746f96090..06d079c27 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class NonOverloadedMethod : CodeViewField { - private readonly LazyVariable _function; + private readonly LazyVariable _function; /// /// Initializes an empty non-overloaded method. @@ -14,7 +14,7 @@ public class NonOverloadedMethod : CodeViewField protected NonOverloadedMethod(uint typeIndex) : base(typeIndex) { - _function = new LazyVariable(GetFunction); + _function = new LazyVariable(GetFunction); } /// @@ -23,10 +23,10 @@ protected NonOverloadedMethod(uint typeIndex) /// The function that is referenced by the method. /// The attributes associated to the method. /// The name of the method. - public NonOverloadedMethod(MemberFunction function, CodeViewFieldAttributes attributes, Utf8String name) + public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes attributes, Utf8String name) : base(0) { - _function = new LazyVariable(function); + _function = new LazyVariable(function); Attributes = attributes; Name = name; } @@ -38,10 +38,10 @@ public NonOverloadedMethod(MemberFunction function, CodeViewFieldAttributes attr /// The attributes associated to the method. /// The name of the method. /// The offset to the slot the virtual function table that this method occupies. - public NonOverloadedMethod(MemberFunction function, CodeViewFieldAttributes attributes, Utf8String name, uint vfTableOffset) + public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes attributes, Utf8String name, uint vfTableOffset) : base(0) { - _function = new LazyVariable(function); + _function = new LazyVariable(function); Attributes = attributes; Name = name; VFTableOffset = vfTableOffset; @@ -53,7 +53,7 @@ public NonOverloadedMethod(MemberFunction function, CodeViewFieldAttributes attr /// /// Gets or sets the function that is referenced by this method. /// - public MemberFunction? Function + public MemberFunctionLeaf? Function { get => _function.Value; set => _function.Value = value; @@ -76,5 +76,5 @@ public uint VFTableOffset /// /// This method is called upon initialization of the property. /// - protected virtual MemberFunction? GetFunction() => null; + protected virtual MemberFunctionLeaf? GetFunction() => null; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs index 5ac395c5b..20d1b3fda 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class OverloadedMethod : CodeViewField { - private readonly LazyVariable _methods; + private readonly LazyVariable _methods; /// /// Initializes an empty overloaded method. @@ -14,7 +14,7 @@ public class OverloadedMethod : CodeViewField protected OverloadedMethod(uint typeIndex) : base(typeIndex) { - _methods = new LazyVariable(GetMethods); + _methods = new LazyVariable(GetMethods); } /// @@ -31,7 +31,7 @@ public OverloadedMethod() /// /// Gets or sets a list of methods that were overloaded. /// - public MethodList? Methods + public MethodListLeaf? Methods { get => _methods.Value; set => _methods.Value = value; @@ -44,5 +44,5 @@ public OverloadedMethod() /// /// This method is called upon initialization of the property. /// - protected virtual MethodList? GetMethods() => null; + protected virtual MethodListLeaf? GetMethods() => null; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs similarity index 95% rename from src/AsmResolver.Symbols.Pdb/Leaves/PointerType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs index 5bda18af4..740e792cd 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/PointerType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs @@ -3,18 +3,18 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a pointer type in a TPI or IPI stream. /// -public class PointerType : CodeViewType +public class PointerTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _baseType; + private readonly LazyVariable _baseType; /// /// Initializes a new empty pointer type. /// /// The type index to assign to the type. - protected PointerType(uint typeIndex) + protected PointerTypeRecord(uint typeIndex) : base(typeIndex) { - _baseType = new LazyVariable(GetBaseType); + _baseType = new LazyVariable(GetBaseType); } /// @@ -22,10 +22,10 @@ protected PointerType(uint typeIndex) /// /// The referent type. /// The attributes describing the shape of the pointer. - public PointerType(CodeViewType type, PointerAttributes attributes) + public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes) : base(0) { - _baseType = new LazyVariable(type); + _baseType = new LazyVariable(type); Attributes = attributes; } @@ -35,10 +35,10 @@ public PointerType(CodeViewType type, PointerAttributes attributes) /// The referent type. /// The attributes describing the shape of the pointer. /// The size of the pointer. - public PointerType(CodeViewType type, PointerAttributes attributes, byte size) + public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes, byte size) : base(0) { - _baseType = new LazyVariable(type); + _baseType = new LazyVariable(type); Attributes = attributes; Size = size; } @@ -49,7 +49,7 @@ public PointerType(CodeViewType type, PointerAttributes attributes, byte size) /// /// Gets or sets the referent type of the pointer. /// - public CodeViewType BaseType + public CodeViewTypeRecord BaseType { get => _baseType.Value; set => _baseType.Value = value; @@ -349,7 +349,7 @@ public bool IsRValueRefThisPointer /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetBaseType() => null; + protected virtual CodeViewTypeRecord? GetBaseType() => null; /// public override string ToString() => $"({BaseType})*"; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs similarity index 72% rename from src/AsmResolver.Symbols.Pdb/Leaves/ProcedureType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs index cfdac7135..b973fcccb 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs @@ -5,20 +5,20 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a function pointer or procedure type. /// -public class ProcedureType : CodeViewType +public class ProcedureTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _returnType; - private readonly LazyVariable _argumentList; + private readonly LazyVariable _returnType; + private readonly LazyVariable _argumentList; /// /// Initializes an empty procedure type. /// /// The type index to assign to the type. - protected ProcedureType(uint typeIndex) + protected ProcedureTypeRecord(uint typeIndex) : base(typeIndex) { - _returnType = new LazyVariable(GetReturnType); - _argumentList = new LazyVariable(GetArguments); + _returnType = new LazyVariable(GetReturnType); + _argumentList = new LazyVariable(GetArguments); } /// @@ -27,12 +27,12 @@ protected ProcedureType(uint typeIndex) /// The convention to use when calling the function pointed by values of this type. /// The return type of the function. /// The argument type list of the function. - public ProcedureType(CodeViewCallingConvention callingConvention, CodeViewType returnType, ArgumentList arguments) + public ProcedureTypeRecord(CodeViewCallingConvention callingConvention, CodeViewTypeRecord returnType, ArgumentListLeaf arguments) : base(0) { CallingConvention = callingConvention; - _returnType = new LazyVariable(returnType); - _argumentList = new LazyVariable(arguments); + _returnType = new LazyVariable(returnType); + _argumentList = new LazyVariable(arguments); } /// @@ -41,7 +41,7 @@ public ProcedureType(CodeViewCallingConvention callingConvention, CodeViewType r /// /// Gets or sets the return type of the function. /// - public CodeViewType? ReturnType + public CodeViewTypeRecord? ReturnType { get => _returnType.Value; set => _returnType.Value = value; @@ -68,7 +68,7 @@ public MemberFunctionAttributes Attributes /// /// Gets or sets the list of types of the parameters that this function defines. /// - public ArgumentList? Arguments + public ArgumentListLeaf? Arguments { get => _argumentList.Value; set => _argumentList.Value = value; @@ -81,7 +81,7 @@ public MemberFunctionAttributes Attributes /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetReturnType() => null; + protected virtual CodeViewTypeRecord? GetReturnType() => null; /// /// Obtains the argument types of the procedure.. @@ -90,12 +90,12 @@ public MemberFunctionAttributes Attributes /// /// This method is called upon initialization of the property. /// - protected virtual ArgumentList? GetArguments() => null; + protected virtual ArgumentListLeaf? GetArguments() => null; /// public override string ToString() { - string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); + string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); return $"{CallingConvention} {ReturnType} *({args})"; } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs similarity index 76% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs index 2663b7f24..a8a54409c 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedArgumentList : ArgumentList +public class SerializedArgumentListLeaf : ArgumentListLeaf { private readonly PdbReaderContext _context; private readonly BinaryStreamReader _reader; @@ -17,7 +17,7 @@ public class SerializedArgumentList : ArgumentList /// The reading context in which the list is situated in. /// The type index to assign to the list. /// The input stream to read from. - public SerializedArgumentList(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedArgumentListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -25,17 +25,17 @@ public SerializedArgumentList(PdbReaderContext context, uint typeIndex, BinarySt } /// - protected override IList GetArgumentTypes() + protected override IList GetArgumentTypes() { var reader = _reader.Fork(); int count = reader.ReadInt32(); - var result = new List(count); + var result = new List(count); for (int i = 0; i < count; i++) { uint typeIndex = reader.ReadUInt32(); - if (!_context.ParentImage.TryGetLeafRecord(typeIndex, out var leaf) || leaf is not CodeViewType t) + if (!_context.ParentImage.TryGetLeafRecord(typeIndex, out var leaf) || leaf is not CodeViewTypeRecord t) { _context.Parameters.ErrorListener.BadImage( $"Argument list {TypeIndex:X8} contains an invalid argument type index {typeIndex:X8}."); diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs similarity index 77% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs index d5ab123a8..273df7536 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedArrayType : ArrayType +public class SerializedArrayTypeRecord : ArrayTypeRecord { private readonly PdbReaderContext _context; private readonly uint _elementTypeIndex; @@ -19,7 +19,7 @@ public class SerializedArrayType : ArrayType /// The reading context in which the type is situated in. /// The type index to assign to the type. /// The input stream to read from. - public SerializedArrayType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedArrayTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -33,20 +33,20 @@ public SerializedArrayType(PdbReaderContext context, uint typeIndex, BinaryStrea protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetElementType() + protected override CodeViewTypeRecord? GetElementType() { - return _context.ParentImage.TryGetLeafRecord(_elementTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_elementTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Array type {TypeIndex:X8} contains an invalid element type index {_elementTypeIndex:X8}."); } /// - protected override CodeViewType? GetIndexType() + protected override CodeViewTypeRecord? GetIndexType() { - return _context.ParentImage.TryGetLeafRecord(_indexTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_indexTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Array type {TypeIndex:X8} contains an invalid index type index {_indexTypeIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClass.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs similarity index 76% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClass.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs index 997ba43ab..325e73010 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClass.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedBaseClass : BaseClass +public class SerializedBaseClassField : BaseClassField { private readonly PdbReaderContext _context; private readonly uint _baseTypeIndex; @@ -17,7 +17,7 @@ public class SerializedBaseClass : BaseClass /// The reading context in which the class is situated in. /// The type index to assign to the type. /// The input stream to read from. - public SerializedBaseClass(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + public SerializedBaseClassField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -27,11 +27,11 @@ public SerializedBaseClass(PdbReaderContext context, uint typeIndex, ref BinaryS } /// - protected override CodeViewType? GetBaseType() + protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Base class {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs similarity index 81% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs index 9f47f719c..3773352ff 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs @@ -3,9 +3,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedClassType : ClassType +public class SerializedClassTypeRecord : ClassTypeRecord { private readonly PdbReaderContext _context; private readonly ushort _memberCount; @@ -22,7 +22,7 @@ public class SerializedClassType : ClassType /// The reading context in which the type is situated in. /// The type index to assign to the type. /// The input stream to read from. - public SerializedClassType(CodeViewLeafKind kind, PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedClassTypeRecord(CodeViewLeafKind kind, PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(kind, typeIndex) { _context = context; @@ -46,38 +46,38 @@ public SerializedClassType(CodeViewLeafKind kind, PdbReaderContext context, uint protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetBaseType() + protected override CodeViewTypeRecord? GetBaseType() { if (_baseTypeIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Class type {TypeIndex:X8} contains an invalid underlying enum type index {_baseTypeIndex:X8}."); } /// - protected override FieldList? GetFields() + protected override FieldListLeaf? GetFields() { if (_fieldIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldList list + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list ? list - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); } /// - protected override VTableShape? GetVTableShape() + protected override VTableShapeLeaf? GetVTableShape() { if (_vTableShapeIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_vTableShapeIndex, out var leaf) && leaf is VTableShape shape + return _context.ParentImage.TryGetLeafRecord(_vTableShapeIndex, out var leaf) && leaf is VTableShapeLeaf shape ? shape - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Class type {TypeIndex:X8} contains an invalid VTable shape index {_fieldIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs similarity index 79% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs index 8427b3d0c..e671bb176 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs @@ -3,9 +3,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedEnumType : EnumType +public class SerializedEnumTypeRecord : EnumTypeRecord { private readonly PdbReaderContext _context; private readonly ushort _memberCount; @@ -19,7 +19,7 @@ public class SerializedEnumType : EnumType /// The reading context in which the symbol is situated in. /// The type index to assign to the enum type. /// The input stream to read from. - public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedEnumTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -34,23 +34,23 @@ public SerializedEnumType(PdbReaderContext context, uint typeIndex, BinaryStream protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetBaseType() + protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}."); } /// - protected override FieldList? GetFields() + protected override FieldListLeaf? GetFields() { if (_fieldIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is FieldList list + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is FieldListLeaf list ? list - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Enum type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs similarity index 89% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs index 8fafa97fe..2eddbc7d3 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -internal class SerializedFieldList : FieldList +internal class SerializedFieldListLeaf : FieldListLeaf { private readonly PdbReaderContext _context; private readonly BinaryStreamReader _reader; @@ -17,7 +17,7 @@ internal class SerializedFieldList : FieldList /// The reading context in which the list is situated in. /// The type index to assign to the enum type. /// The input stream to read from. - public SerializedFieldList(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedFieldListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataMember.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs similarity index 81% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataMember.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs index 44cb7ad43..c5fcf252e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataMember.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedInstanceDataMember : InstanceDataMember +public class SerializedInstanceDataField : InstanceDataField { private readonly PdbReaderContext _context; private readonly uint _dataTypeIndex; @@ -18,7 +18,7 @@ public class SerializedInstanceDataMember : InstanceDataMember /// The reading context in which the member is situated in. /// The type index to assign to the member. /// The input stream to read from. - public SerializedInstanceDataMember(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + public SerializedInstanceDataField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) : base(typeIndex) { Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); @@ -36,14 +36,14 @@ public SerializedInstanceDataMember(PdbReaderContext context, uint typeIndex, re protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetDataType() + protected override CodeViewTypeRecord? GetDataType() { if (_dataTypeIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Instance data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunction.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs similarity index 76% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunction.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs index f3aacd7e3..1b0cc2253 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunction.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs @@ -3,9 +3,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedMemberFunction : MemberFunction +public class SerializedMemberFunctionLeaf : MemberFunctionLeaf { private readonly PdbReaderContext _context; private readonly uint _returnTypeIndex; @@ -20,7 +20,7 @@ public class SerializedMemberFunction : MemberFunction /// The reading context in which the function is situated in. /// The index to assign to the type. /// The input stream to read from. - public SerializedMemberFunction(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedMemberFunctionLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -35,41 +35,41 @@ public SerializedMemberFunction(PdbReaderContext context, uint typeIndex, Binary } /// - protected override CodeViewType? GetReturnType() + protected override CodeViewTypeRecord? GetReturnType() { - return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Member function {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); } /// - protected override CodeViewType? GetDeclaringType() + protected override CodeViewTypeRecord? GetDeclaringType() { - return _context.ParentImage.TryGetLeafRecord(_declaringTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_declaringTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Member function {TypeIndex:X8} contains an invalid declaring type index {_declaringTypeIndex:X8}."); } /// - protected override CodeViewType? GetThisType() + protected override CodeViewTypeRecord? GetThisType() { - return _context.ParentImage.TryGetLeafRecord(_thisTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_thisTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Member function {TypeIndex:X8} contains an invalid this-type index {_thisTypeIndex:X8}."); } /// - protected override ArgumentList? GetArguments() + protected override ArgumentListLeaf? GetArguments() { if (_argumentListIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentList list + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list ? list - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Member function {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs index df97c21cc..a3994042e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs @@ -25,11 +25,11 @@ public SerializedMethodListEntry(PdbReaderContext context, ref BinaryStreamReade } /// - protected override MemberFunction? GetFunction() + protected override MemberFunctionLeaf? GetFunction() { - return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunction type + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Method list entry contains an invalid return type index {_functionIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodList.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs similarity index 83% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodList.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs index 0d42631c1..36927a164 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodList.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedMethodList : MethodList +public class SerializedMethodListLeaf : MethodListLeaf { private readonly PdbReaderContext _context; private readonly BinaryStreamReader _reader; @@ -17,7 +17,7 @@ public class SerializedMethodList : MethodList /// The reading context in which the list is situated in. /// The type index to assign to the enum type. /// The input stream to read from. - public SerializedMethodList(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedMethodListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs similarity index 74% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs index 882263042..a6c9b5bf3 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs @@ -3,9 +3,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedModifierType : ModifierType +public class SerializedModifierTypeRecord : ModifierTypeRecord { private readonly PdbReaderContext _context; private readonly uint _baseTypeIndex; @@ -16,7 +16,7 @@ public class SerializedModifierType : ModifierType /// The reading context in which the type is situated in. /// The index to assign to the type. /// The input stream to read from. - public SerializedModifierType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedModifierTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -25,11 +25,11 @@ public SerializedModifierType(PdbReaderContext context, uint typeIndex, BinarySt } /// - protected override CodeViewType? GetBaseType() + protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Modifier type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs similarity index 78% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs index 7e22a80a4..9ebefafc0 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs @@ -3,9 +3,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedNestedType : NestedType +public class SerializedNestedTypeField : NestedTypeField { private readonly PdbReaderContext _context; private readonly uint _typeIndex; @@ -17,7 +17,7 @@ public class SerializedNestedType : NestedType /// The reading context in which the field is situated in. /// The index to assign to the field. /// The input stream to read from. - public SerializedNestedType(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + public SerializedNestedTypeField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -31,11 +31,11 @@ public SerializedNestedType(PdbReaderContext context, uint typeIndex, ref Binary protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetNestedType() + protected override CodeViewTypeRecord? GetNestedType() { - return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Nested type {TypeIndex:X8} contains an invalid type index {_typeIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs index a92f37e97..3342850e3 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs @@ -32,14 +32,14 @@ public SerializedOverloadedMethod(PdbReaderContext context, uint typeIndex, ref protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override MethodList? GetMethods() + protected override MethodListLeaf? GetMethods() { if (_methodListIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_methodListIndex, out var leaf) && leaf is MethodList list + return _context.ParentImage.TryGetLeafRecord(_methodListIndex, out var leaf) && leaf is MethodListLeaf list ? list - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Overloaded method {TypeIndex:X8} contains an invalid field list index {_methodListIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs similarity index 75% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs index b1098cc33..74a481134 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs @@ -3,9 +3,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedPointerType : PointerType +public class SerializedPointerTypeRecord : PointerTypeRecord { private readonly PdbReaderContext _context; private readonly uint _baseTypeIndex; @@ -16,7 +16,7 @@ public class SerializedPointerType : PointerType /// The reading context in which the type is situated in. /// The index to assign to the type. /// The input stream to read from. - public SerializedPointerType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedPointerTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -27,11 +27,11 @@ public SerializedPointerType(PdbReaderContext context, uint typeIndex, BinaryStr } /// - protected override CodeViewType? GetBaseType() + protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Pointer {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs similarity index 77% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs index 13ec71139..90465a8a9 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs @@ -3,9 +3,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedProcedureType : ProcedureType +public class SerializedProcedureTypeRecord : ProcedureTypeRecord { private readonly PdbReaderContext _context; private readonly uint _returnTypeIndex; @@ -18,7 +18,7 @@ public class SerializedProcedureType : ProcedureType /// The reading context in which the type is situated in. /// The index to assign to the type. /// The input stream to read from. - public SerializedProcedureType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedProcedureTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -30,23 +30,23 @@ public SerializedProcedureType(PdbReaderContext context, uint typeIndex, BinaryS } /// - protected override CodeViewType? GetReturnType() + protected override CodeViewTypeRecord? GetReturnType() { - return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Procedure type {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); } /// - protected override ArgumentList? GetArguments() + protected override ArgumentListLeaf? GetArguments() { if (_argumentListIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentList list + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list ? list - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Procedure type {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs similarity index 83% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs index dda21c034..3a0169a39 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedUnionType : UnionType +public class SerializedUnionTypeRecord : UnionTypeRecord { private readonly PdbReaderContext _context; private readonly ushort _memberCount; @@ -20,7 +20,7 @@ public class SerializedUnionType : UnionType /// The reading context in which the type is situated in. /// The index to assign to the type. /// The input stream to read from. - public SerializedUnionType(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedUnionTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _context = context; @@ -42,14 +42,14 @@ public SerializedUnionType(PdbReaderContext context, uint typeIndex, BinaryStrea protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String(); /// - protected override FieldList? GetFields() + protected override FieldListLeaf? GetFields() { if (_fieldIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldList list + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list ? list - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Union type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs similarity index 86% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs index 6b8878eed..b8b7f0396 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShape.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedVTableShape : VTableShape +public class SerializedVTableShapeLeaf : VTableShapeLeaf { private readonly ushort _count; private readonly BinaryStreamReader _entriesReader; @@ -17,7 +17,7 @@ public class SerializedVTableShape : VTableShape /// The reading context in which the shape is situated in. /// The index to assign to the shape. /// The input stream to read from. - public SerializedVTableShape(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + public SerializedVTableShapeLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) : base(typeIndex) { _count = reader.ReadUInt16(); diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs similarity index 81% rename from src/AsmResolver.Symbols.Pdb/Leaves/SimpleType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs index 9efefe20f..8b36de431 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs @@ -1,15 +1,17 @@ +using System; + namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a simple type referenced by a simple type index. /// -public class SimpleType : CodeViewType +public class SimpleTypeRecord : CodeViewTypeRecord { /// /// Constructs a new simple type based on the provided type index. /// /// The type index. - public SimpleType(uint typeIndex) + public SimpleTypeRecord(uint typeIndex) : base(typeIndex) { } @@ -18,7 +20,7 @@ public SimpleType(uint typeIndex) /// Constructs a new simple type with the provided type kind. /// /// The type kind. - public SimpleType(SimpleTypeKind kind) + public SimpleTypeRecord(SimpleTypeKind kind) : base((uint) kind) { } @@ -28,7 +30,7 @@ public SimpleType(SimpleTypeKind kind) /// /// The type kind. /// The mode indicating the pointer specifiers added to the type. - public SimpleType(SimpleTypeKind kind, SimpleTypeMode mode) + public SimpleTypeRecord(SimpleTypeKind kind, SimpleTypeMode mode) : base((uint) kind | ((uint) mode << 8)) { } @@ -52,5 +54,7 @@ public SimpleType(SimpleTypeKind kind, SimpleTypeMode mode) public bool IsPointer => Mode != SimpleTypeMode.Direct; /// - public override string ToString() => $"{Kind} ({Mode})"; + public override string ToString() => Mode == SimpleTypeMode.Direct + ? Kind.ToString() + : $"{Kind}*"; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnionType.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs similarity index 79% rename from src/AsmResolver.Symbols.Pdb/Leaves/UnionType.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs index 4f4173b15..a28d73ceb 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/UnionType.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a type of a value that may have several representations or formats within the same position of memory. /// -public class UnionType : CodeViewCompositeType +public class UnionTypeRecord : CodeViewCompositeTypeRecord { private readonly LazyVariable _uniqueName; @@ -11,7 +11,7 @@ public class UnionType : CodeViewCompositeType /// Initializes an empty union type. /// /// The type index to assign to the union. - protected UnionType(uint typeIndex) + protected UnionTypeRecord(uint typeIndex) : base(typeIndex) { _uniqueName = new LazyVariable(GetUniqueName); @@ -21,7 +21,7 @@ protected UnionType(uint typeIndex) /// Creates a new union type with the provided size. /// /// The total size in bytes of the union. - public UnionType(ulong size) + public UnionTypeRecord(ulong size) : base(0) { _uniqueName = new LazyVariable(Utf8String.Empty); @@ -49,7 +49,6 @@ public Utf8String UniqueName set => _uniqueName.Value = value; } - /// /// Obtains the uniquely identifiable name of the type. /// @@ -58,4 +57,14 @@ public Utf8String UniqueName /// This method is called upon initialization of the property. /// protected virtual Utf8String? GetUniqueName() => null; + + /// + public override string ToString() + { + if (!Utf8String.IsNullOrEmpty(Name)) + return Name; + if (!Utf8String.IsNullOrEmpty(UniqueName)) + return UniqueName; + return $""; + } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs index 6384067b1..abbb9bba9 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs @@ -1,7 +1,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// -/// Provides members defining all possible types that a single entry in a can be. +/// Provides members defining all possible types that a single entry in a can be. /// public enum VTableShapeEntry : byte { diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShape.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs similarity index 89% rename from src/AsmResolver.Symbols.Pdb/Leaves/VTableShape.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs index 75ab6425e..508664c32 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShape.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs @@ -6,7 +6,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Describes the shape of the virtual function table of a class or structure type. /// -public class VTableShape : CodeViewLeaf +public class VTableShapeLeaf : CodeViewLeaf { private IList? _entries; @@ -14,7 +14,7 @@ public class VTableShape : CodeViewLeaf /// Initializes a new empty virtual function table shape. /// /// The type index to assign to the shape. - protected VTableShape(uint typeIndex) + protected VTableShapeLeaf(uint typeIndex) : base(typeIndex) { } @@ -22,7 +22,7 @@ protected VTableShape(uint typeIndex) /// /// Creates a new empty virtual function table shape. /// - public VTableShape() + public VTableShapeLeaf() : base(0) { } @@ -30,7 +30,7 @@ public VTableShape() /// /// Creates a new virtual function table shape with the provided entries. /// - public VTableShape(params VTableShapeEntry[] entries) + public VTableShapeLeaf(params VTableShapeEntry[] entries) : base(0) { _entries = new List(entries); diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index 7ffde2e25..cb0170dd5 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -16,7 +16,7 @@ namespace AsmResolver.Symbols.Pdb; public class PdbImage { private IList? _symbols; - private ConcurrentDictionary _simpleTypes = new(); + private ConcurrentDictionary _simpleTypes = new(); /// /// Gets a collection of all symbols stored in the PDB image. @@ -91,7 +91,7 @@ public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out Cod typeIndex &= 0x7fffffff; if (typeIndex is > 0 and < 0x1000) { - type = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleType(i)); + type = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleTypeRecord(i)); return true; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs index 795699f5e..5bdc45c4c 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs @@ -8,7 +8,7 @@ namespace AsmResolver.Symbols.Pdb.Records; public class ConstantSymbol : CodeViewSymbol { private readonly LazyVariable _name; - private readonly LazyVariable _type; + private readonly LazyVariable _type; /// /// Initializes a named constant @@ -16,7 +16,7 @@ public class ConstantSymbol : CodeViewSymbol protected ConstantSymbol() { _name = new LazyVariable(GetName); - _type = new LazyVariable(GetConstantType); + _type = new LazyVariable(GetConstantType); } /// @@ -25,10 +25,10 @@ protected ConstantSymbol() /// The name of the type. /// The type. /// The value to assign to the constant. - public ConstantSymbol(Utf8String name, CodeViewType type, ushort value) + public ConstantSymbol(Utf8String name, CodeViewTypeRecord type, ushort value) { _name = new LazyVariable(name); - _type = new LazyVariable(type); + _type = new LazyVariable(type); Value = value; } @@ -38,7 +38,7 @@ public ConstantSymbol(Utf8String name, CodeViewType type, ushort value) /// /// Gets or sets the value type of the constant. /// - public CodeViewType Type + public CodeViewTypeRecord Type { get => _type.Value; set => _type.Value = value; @@ -78,7 +78,7 @@ public Utf8String Name /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetConstantType() => null; + protected virtual CodeViewTypeRecord? GetConstantType() => null; /// public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name} = {Value}"; diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs index 10d5db4c3..3c3c31c53 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs @@ -29,11 +29,11 @@ public SerializedConstantSymbol(PdbReaderContext context, BinaryStreamReader rea protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetConstantType() + protected override CodeViewTypeRecord? GetConstantType() { - return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"Constant contains an invalid type index {_typeIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs index c259ddd65..fdcce9a7f 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs @@ -28,11 +28,11 @@ public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamRea protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); /// - protected override CodeViewType? GetSymbolType() + protected override CodeViewTypeRecord? GetSymbolType() { - return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewType type + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type ? type - : _context.Parameters.ErrorListener.BadImageAndReturn( + : _context.Parameters.ErrorListener.BadImageAndReturn( $"User-defined type contains an invalid type index {_typeIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs index 8f33b3922..4e84a3aa3 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -8,7 +8,7 @@ namespace AsmResolver.Symbols.Pdb.Records; public class UserDefinedTypeSymbol : CodeViewSymbol { private readonly LazyVariable _name; - private readonly LazyVariable _type; + private readonly LazyVariable _type; /// /// Initializes a new empty user-defined type symbol. @@ -16,7 +16,7 @@ public class UserDefinedTypeSymbol : CodeViewSymbol protected UserDefinedTypeSymbol() { _name = new LazyVariable(GetName); - _type = new LazyVariable(GetSymbolType); + _type = new LazyVariable(GetSymbolType); } /// @@ -24,10 +24,10 @@ protected UserDefinedTypeSymbol() /// /// The name of the type. /// The type. - public UserDefinedTypeSymbol(Utf8String name, CodeViewType type) + public UserDefinedTypeSymbol(Utf8String name, CodeViewTypeRecord type) { _name = new LazyVariable(name); - _type = new LazyVariable(type); + _type = new LazyVariable(type); } /// @@ -45,7 +45,7 @@ public Utf8String Name /// /// Gets or sets the index associated to the type. /// - public CodeViewType Type + public CodeViewTypeRecord Type { get => _type.Value; set => _type.Value = value; @@ -67,7 +67,7 @@ public CodeViewType Type /// /// This method is called upon initialization of the property. /// - protected virtual CodeViewType? GetSymbolType() => null; + protected virtual CodeViewTypeRecord? GetSymbolType() => null; /// public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name}"; diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs new file mode 100644 index 000000000..a059b14b4 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs @@ -0,0 +1,23 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ArgumentListLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ArgumentListLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadMultipleTypes() + { + var list = (ArgumentListLeaf) _fixture.SimplePdb.GetLeafRecord(0x2391); + Assert.IsAssignableFrom(list.Types[0]); + Assert.IsAssignableFrom(list.Types[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListTest.cs deleted file mode 100644 index e4731ad9d..000000000 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListTest.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Linq; -using AsmResolver.Symbols.Pdb.Leaves; -using Xunit; - -namespace AsmResolver.Symbols.Pdb.Tests.Leaves; - -public class ArgumentListTest : IClassFixture -{ - private readonly MockPdbFixture _fixture; - - public ArgumentListTest(MockPdbFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public void ReadMultipleTypes() - { - var list = (ArgumentList) _fixture.SimplePdb.GetLeafRecord(0x2391); - Assert.IsAssignableFrom(list.Types[0]); - Assert.IsAssignableFrom(list.Types[1]); - } -} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs similarity index 54% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs index 4202a53e0..f22d763c2 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs @@ -3,11 +3,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class ArrayTypeTest: IClassFixture +public class ArrayTypeRecordTest: IClassFixture { private readonly MockPdbFixture _fixture; - public ArrayTypeTest(MockPdbFixture fixture) + public ArrayTypeRecordTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -15,28 +15,28 @@ public ArrayTypeTest(MockPdbFixture fixture) [Fact] public void ReadElementType() { - var type = (ArrayType) _fixture.SimplePdb.GetLeafRecord(0x1905); - Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(type.ElementType).Kind); + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(type.ElementType).Kind); } [Fact] public void ReadIndexType() { - var type = (ArrayType) _fixture.SimplePdb.GetLeafRecord(0x1905); - Assert.Equal(SimpleTypeKind.UInt32Long, Assert.IsAssignableFrom(type.IndexType).Kind); + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(SimpleTypeKind.UInt32Long, Assert.IsAssignableFrom(type.IndexType).Kind); } [Fact] public void ReadLength() { - var type = (ArrayType) _fixture.SimplePdb.GetLeafRecord(0x1905); + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); Assert.Equal(4u, type.Length); } [Fact] public void ReadEmptyName() { - var type = (ArrayType) _fixture.SimplePdb.GetLeafRecord(0x1905); + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); Assert.True(Utf8String.IsNullOrEmpty(type.Name)); } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs similarity index 76% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs index 670c60bfe..e51e80d1e 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs @@ -4,11 +4,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class ClassTypeTest : IClassFixture +public class ClassTypeRecordTest : IClassFixture { private readonly MockPdbFixture _fixture; - public ClassTypeTest(MockPdbFixture fixture) + public ClassTypeRecordTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -19,7 +19,7 @@ public ClassTypeTest(MockPdbFixture fixture) [InlineData(CodeViewLeafKind.Interface)] public void CreateNewValidType(CodeViewLeafKind kind) { - var type = new ClassType(kind, "MyType", "MyUniqueType", 4, StructureAttributes.FwdRef, null); + var type = new ClassTypeRecord(kind, "MyType", "MyUniqueType", 4, StructureAttributes.FwdRef, null); Assert.Equal(kind, type.LeafKind); Assert.Equal("MyType", type.Name); Assert.Equal("MyUniqueType", type.UniqueName); @@ -31,7 +31,7 @@ public void CreateNewValidType(CodeViewLeafKind kind) [Fact] public void CreateNonValidType() { - Assert.Throws(() => new ClassType( + Assert.Throws(() => new ClassTypeRecord( CodeViewLeafKind.Char, "Invalid", "Invalid", @@ -43,14 +43,14 @@ public void CreateNonValidType() [Fact] public void ReadFieldList() { - var type = (ClassType) _fixture.SimplePdb.GetLeafRecord(0x101b); + var type = (ClassTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x101b); Assert.NotNull(type.Fields); } [Fact] public void ReadVTableShape() { - var type = (ClassType) _fixture.SimplePdb.GetLeafRecord(0x239f); + var type = (ClassTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x239f); Assert.NotNull(type.VTableShape); Assert.Equal(new[] { diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs similarity index 81% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs index 8a8a4b74c..5bff46dd0 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs @@ -4,11 +4,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class EnumTypeTest : IClassFixture +public class EnumTypeRecordTest : IClassFixture { private readonly MockPdbFixture _fixture; - public EnumTypeTest(MockPdbFixture fixture) + public EnumTypeRecordTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -17,7 +17,7 @@ public EnumTypeTest(MockPdbFixture fixture) public void FieldList() { var leaf = _fixture.SimplePdb.GetLeafRecord(0x1009); - var fields = Assert.IsAssignableFrom(leaf).Fields.Entries.Cast().ToArray(); + var fields = Assert.IsAssignableFrom(leaf).Fields.Entries.Cast().ToArray(); var names = new[] { "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs similarity index 79% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs index 88f62e4ab..0fad54239 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs @@ -5,11 +5,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class FieldListTest : IClassFixture +public class FieldListLeafTest : IClassFixture { private readonly MockPdbFixture _fixture; - public FieldListTest(MockPdbFixture fixture) + public FieldListLeafTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -17,7 +17,7 @@ public FieldListTest(MockPdbFixture fixture) [Fact] public void ReadEnumerateList() { - var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x1008); + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1008); var enumerates = list.Entries .Cast() .Select(f => (f.Attributes, f.Name.Value, f.Value)) @@ -34,9 +34,9 @@ public void ReadEnumerateList() [Fact] public void ReadInstanceDataMemberList() { - var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x1017); + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1017); var enumerates = list.Entries - .Cast() + .Cast() .Select(f => (f.Attributes, f.Name.Value, f.Offset)) .ToArray(); @@ -57,10 +57,10 @@ public void ReadInstanceDataMemberList() [Fact] public void ReadMethodsAndBaseClass() { - var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x239d); + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x239d); - Assert.Equal("std::exception", Assert.IsAssignableFrom( - Assert.IsAssignableFrom(list.Entries[0]).Type).Name); + Assert.Equal("std::exception", Assert.IsAssignableFrom( + Assert.IsAssignableFrom(list.Entries[0]).Type).Name); Assert.Equal("bad_cast", Assert.IsAssignableFrom(list.Entries[1]).Name); Assert.Equal("__construct_from_string_literal", Assert.IsAssignableFrom(list.Entries[2]).Name); Assert.Equal("~bad_cast", Assert.IsAssignableFrom(list.Entries[3]).Name); @@ -72,11 +72,11 @@ public void ReadMethodsAndBaseClass() [Fact] public void ReadNestedTypes() { - var list = (FieldList) _fixture.SimplePdb.GetLeafRecord(0x1854); + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1854); Assert.Equal("_LDT_ENTRY::::", - Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[0]).Type).Name); + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[0]).Type).Name); Assert.Equal("_LDT_ENTRY::::", - Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[2]).Type).Name); + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[2]).Type).Name); } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionTest.cs deleted file mode 100644 index 21f6a7a01..000000000 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionTest.cs +++ /dev/null @@ -1,44 +0,0 @@ -using AsmResolver.Symbols.Pdb.Leaves; -using Xunit; - -namespace AsmResolver.Symbols.Pdb.Tests.Leaves; - -public class FunctionTest : IClassFixture -{ - private readonly MockPdbFixture _fixture; - - public FunctionTest(MockPdbFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public void ReadReturnType() - { - var function = (MemberFunction) _fixture.SimplePdb.GetLeafRecord(0x2392); - Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(function.ReturnType).Kind); - } - - [Fact] - public void ReadDeclaringType() - { - var function = (MemberFunction) _fixture.SimplePdb.GetLeafRecord(0x2392); - Assert.Equal("std::bad_cast", Assert.IsAssignableFrom(function.DeclaringType).Name); - } - - [Fact] - public void ReadNonNullThisType() - { - var function = (MemberFunction) _fixture.SimplePdb.GetLeafRecord(0x2392); - Assert.IsAssignableFrom(function.ThisType); - } - - [Fact] - public void ReadArgumentList() - { - var function = (MemberFunction) _fixture.SimplePdb.GetLeafRecord(0x2392); - Assert.NotNull(function.Arguments); - Assert.IsAssignableFrom(function.Arguments!.Types[0]); - Assert.IsAssignableFrom(function.Arguments.Types[1]); - } -} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs new file mode 100644 index 000000000..465f27203 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs @@ -0,0 +1,44 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class MemberFunctionLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public MemberFunctionLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadReturnType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(function.ReturnType).Kind); + } + + [Fact] + public void ReadDeclaringType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.Equal("std::bad_cast", Assert.IsAssignableFrom(function.DeclaringType).Name); + } + + [Fact] + public void ReadNonNullThisType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.IsAssignableFrom(function.ThisType); + } + + [Fact] + public void ReadArgumentList() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.NotNull(function.Arguments); + Assert.IsAssignableFrom(function.Arguments!.Types[0]); + Assert.IsAssignableFrom(function.Arguments.Types[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs similarity index 76% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs index 73003207b..53941c44f 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs @@ -5,11 +5,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class MethodListTest : IClassFixture +public class MethodListLeafTest : IClassFixture { private readonly MockPdbFixture _fixture; - public MethodListTest(MockPdbFixture fixture) + public MethodListLeafTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -17,7 +17,7 @@ public MethodListTest(MockPdbFixture fixture) [Fact] public void ReadNonIntroVirtualEntries() { - var list = (MethodList) _fixture.SimplePdb.GetLeafRecord(0x2394); + var list = (MethodListLeaf) _fixture.SimplePdb.GetLeafRecord(0x2394); var entries = list.Entries; Assert.Equal(new[] diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs similarity index 55% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs index f4043eaa7..e9a03fc40 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs @@ -3,11 +3,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class ModifierTypeTest : IClassFixture +public class ModifierTypeRecordTest : IClassFixture { private readonly MockPdbFixture _fixture; - public ModifierTypeTest(MockPdbFixture fixture) + public ModifierTypeRecordTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -15,21 +15,21 @@ public ModifierTypeTest(MockPdbFixture fixture) [Fact] public void CreateNewType() { - var type = new ModifierType(new SimpleType(SimpleTypeKind.Character8), ModifierAttributes.Const); + var type = new ModifierTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), ModifierAttributes.Const); Assert.True(type.IsConst); } [Fact] public void ReadAttributes() { - var type = (ModifierType) _fixture.SimplePdb.GetLeafRecord(0x1011); + var type = (ModifierTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1011); Assert.True(type.IsConst); } [Fact] public void ReadBaseType() { - var type = (ModifierType) _fixture.SimplePdb.GetLeafRecord(0x1011); + var type = (ModifierTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1011); Assert.Equal(CodeViewLeafKind.Structure, type.BaseType.LeafKind); } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs similarity index 56% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs index 08aa946a9..854c560b6 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs @@ -3,11 +3,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class PointerTypeTest : IClassFixture +public class PointerTypeRecordTest : IClassFixture { private readonly MockPdbFixture _fixture; - public PointerTypeTest(MockPdbFixture fixture) + public PointerTypeRecordTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -15,7 +15,7 @@ public PointerTypeTest(MockPdbFixture fixture) [Fact] public void CreateNewType() { - var type = new PointerType(new SimpleType(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); Assert.True(type.IsConst); Assert.Equal(4, type.Size); } @@ -23,7 +23,7 @@ public void CreateNewType() [Fact] public void UpdateKind() { - var type = new PointerType(new SimpleType(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); type.Kind = PointerAttributes.Near32; Assert.Equal(PointerAttributes.Near32, type.Kind); } @@ -31,7 +31,7 @@ public void UpdateKind() [Fact] public void UpdateMode() { - var type = new PointerType(new SimpleType(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); type.Mode = PointerAttributes.LValueReference; Assert.Equal(PointerAttributes.LValueReference, type.Mode); } @@ -39,7 +39,7 @@ public void UpdateMode() [Fact] public void UpdateSize() { - var type = new PointerType(new SimpleType(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); type.Size = 8; Assert.Equal(8, type.Size); } @@ -47,14 +47,14 @@ public void UpdateSize() [Fact] public void ReadAttributes() { - var type = (PointerType) _fixture.SimplePdb.GetLeafRecord(0x1012); + var type = (PointerTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1012); Assert.True(type.IsNear32); } [Fact] public void ReadBaseType() { - var type = (PointerType) _fixture.SimplePdb.GetLeafRecord(0x1012); + var type = (PointerTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1012); Assert.Equal(CodeViewLeafKind.Modifier, type.BaseType.LeafKind); } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs similarity index 58% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs index f22798f28..4067d6f7d 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs @@ -3,11 +3,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class ProcedureTypeTest : IClassFixture +public class ProcedureTypeRecordTest : IClassFixture { private readonly MockPdbFixture _fixture; - public ProcedureTypeTest(MockPdbFixture fixture) + public ProcedureTypeRecordTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -15,21 +15,21 @@ public ProcedureTypeTest(MockPdbFixture fixture) [Fact] public void ReadReturnType() { - var procedure = (ProcedureType) _fixture.SimplePdb.GetLeafRecord(0x18f7); - Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(procedure.ReturnType).Kind); + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(procedure.ReturnType).Kind); } [Fact] public void ReadCallingConvention() { - var procedure = (ProcedureType) _fixture.SimplePdb.GetLeafRecord(0x18f7); + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); Assert.Equal(CodeViewCallingConvention.NearStd, procedure.CallingConvention); } [Fact] public void ReadArguments() { - var procedure = (ProcedureType) _fixture.SimplePdb.GetLeafRecord(0x18f7); + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); Assert.NotNull(procedure.Arguments); Assert.Equal(2, procedure.Arguments!.Types.Count); } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs similarity index 83% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs index 9b0e44f8b..ac2a784c7 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs @@ -3,14 +3,14 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class SimpleTypeTest +public class SimpleTypeRecordTest { [Theory] [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] public void TypeIndexParsing(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) { - var type = new SimpleType(typeIndex); + var type = new SimpleTypeRecord(typeIndex); Assert.Equal(kind, type.Kind); Assert.Equal(mode, type.Mode); } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs new file mode 100644 index 000000000..75f7c747c --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs @@ -0,0 +1,47 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class UnionTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public UnionTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadSize() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal(4ul, type.Size); + } + + [Fact] + public void ReadName() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal("_LDT_ENTRY::", type.Name); + } + + [Fact] + public void ReadUniqueName() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal(".?AT@_LDT_ENTRY@@", type.UniqueName); + } + + [Fact] + public void ReadFieldList() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + var fields = type.Fields!; + Assert.NotNull(fields); + Assert.IsAssignableFrom(fields.Entries[0]); + Assert.IsAssignableFrom(fields.Entries[1]); + Assert.IsAssignableFrom(fields.Entries[2]); + Assert.IsAssignableFrom(fields.Entries[3]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeTest.cs deleted file mode 100644 index 2279f08ac..000000000 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -using AsmResolver.Symbols.Pdb.Leaves; -using Xunit; - -namespace AsmResolver.Symbols.Pdb.Tests.Leaves; - -public class UnionTypeTest : IClassFixture -{ - private readonly MockPdbFixture _fixture; - - public UnionTypeTest(MockPdbFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public void ReadSize() - { - var type = (UnionType) _fixture.SimplePdb.GetLeafRecord(0x1855); - Assert.Equal(4ul, type.Size); - } - - [Fact] - public void ReadName() - { - var type = (UnionType) _fixture.SimplePdb.GetLeafRecord(0x1855); - Assert.Equal("_LDT_ENTRY::", type.Name); - } - - [Fact] - public void ReadUniqueName() - { - var type = (UnionType) _fixture.SimplePdb.GetLeafRecord(0x1855); - Assert.Equal(".?AT@_LDT_ENTRY@@", type.UniqueName); - } - - [Fact] - public void ReadFieldList() - { - var type = (UnionType) _fixture.SimplePdb.GetLeafRecord(0x1855); - var fields = type.Fields!; - Assert.NotNull(fields); - Assert.IsAssignableFrom(fields.Entries[0]); - Assert.IsAssignableFrom(fields.Entries[1]); - Assert.IsAssignableFrom(fields.Entries[2]); - Assert.IsAssignableFrom(fields.Entries[3]); - } -} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs similarity index 70% rename from test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs index ec5a1a005..929183c76 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs @@ -3,11 +3,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class VTableShapeTest : IClassFixture +public class VTableShapeLeafTest : IClassFixture { private readonly MockPdbFixture _fixture; - public VTableShapeTest(MockPdbFixture fixture) + public VTableShapeLeafTest(MockPdbFixture fixture) { _fixture = fixture; } @@ -17,7 +17,7 @@ public VTableShapeTest(MockPdbFixture fixture) [InlineData(0x239e, new[] { VTableShapeEntry.Near32,VTableShapeEntry.Near32 })] public void ReadEntries(uint typeIndex, VTableShapeEntry[] expectedEntries) { - var shape = (VTableShape) _fixture.SimplePdb.GetLeafRecord(typeIndex); + var shape = (VTableShapeLeaf) _fixture.SimplePdb.GetLeafRecord(typeIndex); Assert.Equal(expectedEntries, shape.Entries); } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs index d166893a3..b1e8cfc2d 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs @@ -17,7 +17,7 @@ public PdbImageTest(MockPdbFixture fixture) [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] public void SimpleTypeLookup(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) { - var type = Assert.IsAssignableFrom(_fixture.SimplePdb.GetLeafRecord(typeIndex)); + var type = Assert.IsAssignableFrom(_fixture.SimplePdb.GetLeafRecord(typeIndex)); Assert.Equal(kind, type.Kind); Assert.Equal(mode, type.Mode); } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs index 5b7365220..bc0e8614e 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs @@ -25,7 +25,7 @@ public void Name() public void Type() { var udt = _fixture.SimplePdb.Symbols.OfType().First(); - var type = Assert.IsAssignableFrom(udt.Type); + var type = Assert.IsAssignableFrom(udt.Type); Assert.Equal(SimpleTypeKind.UInt32, type.Kind); } From 6ac9ceec759c23c209e19b30ed81c68e02292667 Mon Sep 17 00:00:00 2001 From: CursedLand Date: Sat, 6 Aug 2022 23:52:02 +0200 Subject: [PATCH 075/182] Added Callbacks implementation for MemberCloner. --- .../Cloning/CallbackCloneListener.cs | 22 ++++++++++ .../Cloning/IMemberClonerListener.cs | 15 +++++++ .../Cloning/MemberCloner.Fields.cs | 6 +++ .../Cloning/MemberCloner.Methods.cs | 6 +++ .../Cloning/MemberCloner.Semantics.cs | 8 ++++ .../Cloning/MemberCloner.cs | 21 +++++++++- .../Cloning/MemberClonerListener.cs | 41 +++++++++++++++++++ .../Cloning/MetadataClonerTest.cs | 30 +++++++++++++- 8 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs create mode 100644 src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs create mode 100644 src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs diff --git a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs new file mode 100644 index 000000000..be7e1d945 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs @@ -0,0 +1,22 @@ +using System; + +namespace AsmResolver.DotNet.Cloning +{ + /// + public class CallbackCloneListener : IMemberClonerListener + { + + private readonly Action _callback; + + /// + /// Creates a new instance of the class. + /// + /// The Callback used. + public CallbackCloneListener(Action callback) => + _callback = callback; + + /// + public void OnClonedMember(IMetadataMember original, IMetadataMember cloned) => + _callback(original, cloned); + } +} diff --git a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs new file mode 100644 index 000000000..932723b41 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs @@ -0,0 +1,15 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Callback Listener. + /// + public interface IMemberClonerListener + { + /// + /// This function is called for every member got cloned. + /// + /// original member. + /// cloned member. + public void OnClonedMember(IMetadataMember original, IMetadataMember cloned); + } +} diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs index b68ab9c81..c797dfb48 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs @@ -39,7 +39,13 @@ private static FieldDefinition CreateFieldStub(MemberCloneContext context, Field private void DeepCopyFields(MemberCloneContext context) { foreach (var field in _fieldsToClone) + { DeepCopyField(context, field); + var clonedMember = (FieldDefinition)context.ClonedMembers[field]; + _clonerListener.OnClonedMember(field, clonedMember); + if (_clonerListener is MemberClonerListener listener) + listener.OnClonedField(field, clonedMember); + } } private void DeepCopyField(MemberCloneContext context, FieldDefinition field) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs index a7c2dc227..12c1f635f 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs @@ -54,7 +54,13 @@ private static ParameterDefinition CloneParameterDefinition(MemberCloneContext c private void DeepCopyMethods(MemberCloneContext context) { foreach (var method in _methodsToClone) + { DeepCopyMethod(context, method); + var clonedMember = (MethodDefinition)context.ClonedMembers[method]; + _clonerListener.OnClonedMember(method, clonedMember); + if (_clonerListener is MemberClonerListener listener) + listener.OnClonedMethod(method, clonedMember); + } } private void DeepCopyMethod(MemberCloneContext context, MethodDefinition method) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs index 10f3f30c4..c89fa9a4a 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs @@ -17,6 +17,10 @@ private void DeepCopyProperties(MemberCloneContext context) { declaringType.Properties.Add(clonedProperty); } + var clonedMember = clonedProperty; + _clonerListener.OnClonedMember(property, clonedMember); + if (_clonerListener is MemberClonerListener listener) + listener.OnClonedProperty(property, clonedMember); } } @@ -51,6 +55,10 @@ private void DeepCopyEvents(MemberCloneContext context) { declaringType.Events.Add(clonedEvent); } + var clonedMember = clonedEvent; + _clonerListener.OnClonedMember(@event, clonedMember); + if (_clonerListener is MemberClonerListener listener) + listener.OnClonedEvent(@event, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 1580d1efa..7a7ee0da1 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -17,6 +17,7 @@ namespace AsmResolver.DotNet.Cloning /// public partial class MemberCloner { + private readonly IMemberClonerListener _clonerListener; private readonly Func? _importerFactory; private readonly ModuleDefinition _targetModule; @@ -30,7 +31,15 @@ public partial class MemberCloner /// Creates a new instance of the class. /// /// The target module to copy the members into. - public MemberCloner(ModuleDefinition targetModule) : this(targetModule, null) { } + /// The callback used in the cloner listener. + public MemberCloner(ModuleDefinition targetModule, Action? callback = null) : this(targetModule, null, new CallbackCloneListener(callback ?? new Action((orginal, cloned) => { }))) { } + + /// + /// Creates a new instance of the class. + /// + /// The target module to copy the members into. + /// The callback listener used in the cloner. + public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listener) : this(targetModule, null, listener) { } /// /// Creates a new instance of the class. @@ -38,10 +47,12 @@ public partial class MemberCloner /// The target module to copy the members into. /// The factory for creating the reference importer public MemberCloner(ModuleDefinition targetModule, - Func? importerFactory) + Func? importerFactory, + IMemberClonerListener clonerListener) { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); _importerFactory = importerFactory; + _clonerListener = clonerListener; } /// @@ -272,7 +283,13 @@ private void DeepCopyMembers(MemberCloneContext context) private void DeepCopyTypes(MemberCloneContext context) { foreach (var type in _typesToClone) + { DeepCopyType(context, type); + var clonedMember = (TypeDefinition)context.ClonedMembers[type]; + _clonerListener.OnClonedMember(type, clonedMember); + if (_clonerListener is MemberClonerListener listener) + listener.OnClonedType(type, clonedMember); + } } private void DeepCopyType(MemberCloneContext context, TypeDefinition type) diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs new file mode 100644 index 000000000..84c5b522d --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs @@ -0,0 +1,41 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Callback Listener. + /// + public abstract class MemberClonerListener : IMemberClonerListener + { + /// + public abstract void OnClonedMember(IMetadataMember original, IMetadataMember cloned); + /// + /// This function is called for every type got cloned. + /// + /// original type. + /// cloned type. + public abstract void OnClonedType(TypeDefinition original, TypeDefinition cloned); + /// + /// This function is called for every method got cloned. + /// + /// original method. + /// cloned method. + public abstract void OnClonedMethod(MethodDefinition original, MethodDefinition cloned); + /// + /// This function is called for every field got cloned. + /// + /// original field. + /// cloned field. + public abstract void OnClonedField(FieldDefinition original, FieldDefinition cloned); + /// + /// This function is called for every property got cloned. + /// + /// original property. + /// cloned property. + public abstract void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned); + /// + /// This function is called for every event got cloned. + /// + /// original event. + /// cloned event. + public abstract void OnClonedEvent(EventDefinition original, EventDefinition cloned); + } +} diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index fadcc2b51..41441e4f9 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -329,5 +329,33 @@ public void CloneInterfaceImplementations() originalTypeDef.Interfaces.Select(t => t.Interface.FullName), clonedType.Interfaces.Select(t => t.Interface.FullName)); } + + [Fact] + public void CloneCallbackResult() + { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); + var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); + + var targetModule = PrepareTempModule(); + + var reverseMethodsNames = (IMetadataMember original, IMetadataMember cloned) => { + + if (cloned is MethodDefinition clonedDescriptor && original is MethodDefinition originalDescriptor) + clonedDescriptor.Name = new string(originalDescriptor.Name.Reverse().ToArray()); + + }; + + var result = new MemberCloner(targetModule, reverseMethodsNames) + .Include(type) + .Clone(); + + var clonedType = result.GetClonedMember(type); + + Assert.Equal( + type.Methods.Select(m => m.Name.Reverse().ToArray()), + clonedType.Methods.Select(m => m.Name.ToArray())); + + } + } -} \ No newline at end of file +} From 4422b63c4814c69d6cf5c9335c35c76910edecd1 Mon Sep 17 00:00:00 2001 From: CursedLand Date: Sun, 7 Aug 2022 00:06:42 +0200 Subject: [PATCH 076/182] Add missing xmldocs. --- src/AsmResolver.DotNet/Cloning/MemberCloner.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 7a7ee0da1..208f6c273 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -46,6 +46,7 @@ public partial class MemberCloner /// /// The target module to copy the members into. /// The factory for creating the reference importer + /// The listener used in the cloner. public MemberCloner(ModuleDefinition targetModule, Func? importerFactory, IMemberClonerListener clonerListener) From c1e163946c9735b7a319576b1e4952ab402d5332 Mon Sep 17 00:00:00 2001 From: CursedLand Date: Sun, 7 Aug 2022 14:32:19 +0200 Subject: [PATCH 077/182] Add CustomMemberClonerListener tests. --- .../Cloning/MetadataClonerTest.cs | 20 ++++++++++++++ .../AsmResolver.Tests.csproj | 1 + .../Listeners/CustomMemberClonerListener.cs | 26 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index 41441e4f9..6f96ea9b1 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -10,6 +10,7 @@ using AsmResolver.DotNet.TestCases.Methods; using AsmResolver.DotNet.TestCases.Types; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.Tests.Listeners; using AsmResolver.Tests.Runners; using Xunit; @@ -357,5 +358,24 @@ public void CloneCallbackResult() } + [Fact] + public void CloneCustomListenerResult() { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); + var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); + + var targetModule = PrepareTempModule(); + + var result = new MemberCloner(targetModule, new CustomMemberClonerListener()) + .Include(type) + .Clone(); + + var clonedType = result.GetClonedMember(type); + + Assert.Equal( + type.Methods.Select(m => $"Method_{m.Name}"), + clonedType.Methods.Select(m => m.Name.ToString())); + + } + } } diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 82f9cb9c6..122b6f398 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -17,6 +17,7 @@ + diff --git a/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs b/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs new file mode 100644 index 000000000..bcb386929 --- /dev/null +++ b/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs @@ -0,0 +1,26 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Cloning; +using System; + +namespace AsmResolver.Tests.Listeners { + public class CustomMemberClonerListener : MemberClonerListener { + public override void OnClonedEvent(EventDefinition original, EventDefinition cloned) => + cloned.Name = $"Event_{original.Name}"; + + public override void OnClonedField(FieldDefinition original, FieldDefinition cloned) => + cloned.Name = $"Field_{original.Name}"; + + public override void OnClonedMember(IMetadataMember original, IMetadataMember cloned) { + + } + + public override void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) => + cloned.Name = $"Method_{original.Name}"; + + public override void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned) => + cloned.Name = $"Property_{original.Name}"; + + public override void OnClonedType(TypeDefinition original, TypeDefinition cloned) => + cloned.Name = $"Type_{original.Name}"; + } +} From 17e14ba10639c0c39bffa35e6e94ea9bfc180f99 Mon Sep 17 00:00:00 2001 From: CursedLand Date: Sun, 7 Aug 2022 22:49:16 +0200 Subject: [PATCH 078/182] Changes Request Applied. --- .../Cloning/CallbackCloneListener.cs | 8 +-- .../Cloning/IMemberClonerListener.cs | 38 ++++++++++++-- .../Cloning/MemberCloner.Fields.cs | 3 +- .../Cloning/MemberCloner.Methods.cs | 3 +- .../Cloning/MemberCloner.Semantics.cs | 6 +-- .../Cloning/MemberCloner.cs | 11 +++-- .../Cloning/MemberClonerListener.cs | 49 +++++-------------- .../Cloning/MetadataClonerTest.cs | 4 +- .../Listeners/CustomMemberClonerListener.cs | 22 ++------- 9 files changed, 71 insertions(+), 73 deletions(-) diff --git a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs index be7e1d945..efa0063fd 100644 --- a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs +++ b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs @@ -2,8 +2,10 @@ namespace AsmResolver.DotNet.Cloning { - /// - public class CallbackCloneListener : IMemberClonerListener + /// + /// This implementation that calls the to a callback action. + /// + public class CallbackCloneListener : MemberClonerListener { private readonly Action _callback; @@ -16,7 +18,7 @@ public class CallbackCloneListener : IMemberClonerListener _callback = callback; /// - public void OnClonedMember(IMetadataMember original, IMetadataMember cloned) => + public override void OnClonedMember(IMetadataMember original, IMetadataMember cloned) => _callback(original, cloned); } } diff --git a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs index 932723b41..421839b9a 100644 --- a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs +++ b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs @@ -1,15 +1,45 @@ namespace AsmResolver.DotNet.Cloning { /// - /// Callback Listener. + /// Callback listener that receives calls after cloning process. /// public interface IMemberClonerListener { /// - /// This function is called for every member got cloned. + /// This function is called for every member that got cloned. /// - /// original member. - /// cloned member. + /// The original member. + /// The cloned member. public void OnClonedMember(IMetadataMember original, IMetadataMember cloned); + /// + /// This function is called for every type got cloned. + /// + /// The original type. + /// The cloned type. + public void OnClonedType(TypeDefinition original, TypeDefinition cloned); + /// + /// This function is called for every method got cloned. + /// + /// The original method. + /// The cloned method. + public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned); + /// + /// This function is called for every field got cloned. + /// + /// The original field. + /// The cloned field. + public void OnClonedField(FieldDefinition original, FieldDefinition cloned); + /// + /// This function is called for every property got cloned. + /// + /// The original property. + /// The cloned property. + public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned); + /// + /// This function is called for every event got cloned. + /// + /// The original event. + /// The cloned event. + public void OnClonedEvent(EventDefinition original, EventDefinition cloned); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs index c797dfb48..78df400d3 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs @@ -43,8 +43,7 @@ private void DeepCopyFields(MemberCloneContext context) DeepCopyField(context, field); var clonedMember = (FieldDefinition)context.ClonedMembers[field]; _clonerListener.OnClonedMember(field, clonedMember); - if (_clonerListener is MemberClonerListener listener) - listener.OnClonedField(field, clonedMember); + _clonerListener.OnClonedField(field, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs index 12c1f635f..af6085a14 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs @@ -58,8 +58,7 @@ private void DeepCopyMethods(MemberCloneContext context) DeepCopyMethod(context, method); var clonedMember = (MethodDefinition)context.ClonedMembers[method]; _clonerListener.OnClonedMember(method, clonedMember); - if (_clonerListener is MemberClonerListener listener) - listener.OnClonedMethod(method, clonedMember); + _clonerListener.OnClonedMethod(method, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs index c89fa9a4a..7d3e5ea6b 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs @@ -19,8 +19,7 @@ private void DeepCopyProperties(MemberCloneContext context) } var clonedMember = clonedProperty; _clonerListener.OnClonedMember(property, clonedMember); - if (_clonerListener is MemberClonerListener listener) - listener.OnClonedProperty(property, clonedMember); + _clonerListener.OnClonedProperty(property, clonedMember); } } @@ -57,8 +56,7 @@ private void DeepCopyEvents(MemberCloneContext context) } var clonedMember = clonedEvent; _clonerListener.OnClonedMember(@event, clonedMember); - if (_clonerListener is MemberClonerListener listener) - listener.OnClonedEvent(@event, clonedMember); + _clonerListener.OnClonedEvent(@event, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 208f6c273..af5d27abd 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -27,12 +27,18 @@ 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, (original, cloned) => { }) { } + /// /// Creates a new instance of the class. /// /// The target module to copy the members into. /// The callback used in the cloner listener. - public MemberCloner(ModuleDefinition targetModule, Action? callback = null) : this(targetModule, null, new CallbackCloneListener(callback ?? new Action((orginal, cloned) => { }))) { } + public MemberCloner(ModuleDefinition targetModule, Action callback) : this(targetModule, new CallbackCloneListener(callback)) { } /// /// Creates a new instance of the class. @@ -288,8 +294,7 @@ private void DeepCopyTypes(MemberCloneContext context) DeepCopyType(context, type); var clonedMember = (TypeDefinition)context.ClonedMembers[type]; _clonerListener.OnClonedMember(type, clonedMember); - if (_clonerListener is MemberClonerListener listener) - listener.OnClonedType(type, clonedMember); + _clonerListener.OnClonedType(type, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs index 84c5b522d..6f5531fed 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs @@ -1,41 +1,18 @@ namespace AsmResolver.DotNet.Cloning { - /// - /// Callback Listener. - /// - public abstract class MemberClonerListener : IMemberClonerListener - { + /// + public abstract class MemberClonerListener : IMemberClonerListener { /// - public abstract void OnClonedMember(IMetadataMember original, IMetadataMember cloned); - /// - /// This function is called for every type got cloned. - /// - /// original type. - /// cloned type. - public abstract void OnClonedType(TypeDefinition original, TypeDefinition cloned); - /// - /// This function is called for every method got cloned. - /// - /// original method. - /// cloned method. - public abstract void OnClonedMethod(MethodDefinition original, MethodDefinition cloned); - /// - /// This function is called for every field got cloned. - /// - /// original field. - /// cloned field. - public abstract void OnClonedField(FieldDefinition original, FieldDefinition cloned); - /// - /// This function is called for every property got cloned. - /// - /// original property. - /// cloned property. - public abstract void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned); - /// - /// This function is called for every event got cloned. - /// - /// original event. - /// cloned event. - public abstract void OnClonedEvent(EventDefinition original, EventDefinition cloned); + public virtual void OnClonedMember(IMetadataMember original, IMetadataMember cloned) { } + /// + public virtual void OnClonedEvent(EventDefinition original, EventDefinition cloned) { } + /// + public virtual void OnClonedField(FieldDefinition original, FieldDefinition cloned) { } + /// + public virtual void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) { } + /// + public virtual void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned) { } + /// + public virtual void OnClonedType(TypeDefinition original, TypeDefinition cloned) { } } } diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index 6f96ea9b1..9a8e72d2e 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -359,7 +359,9 @@ public void CloneCallbackResult() } [Fact] - public void CloneCustomListenerResult() { + public void CloneCustomListenerResult() + { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); diff --git a/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs b/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs index bcb386929..0afb61f6d 100644 --- a/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs +++ b/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs @@ -2,25 +2,11 @@ using AsmResolver.DotNet.Cloning; using System; -namespace AsmResolver.Tests.Listeners { - public class CustomMemberClonerListener : MemberClonerListener { - public override void OnClonedEvent(EventDefinition original, EventDefinition cloned) => - cloned.Name = $"Event_{original.Name}"; - - public override void OnClonedField(FieldDefinition original, FieldDefinition cloned) => - cloned.Name = $"Field_{original.Name}"; - - public override void OnClonedMember(IMetadataMember original, IMetadataMember cloned) { - - } - +namespace AsmResolver.Tests.Listeners +{ + public class CustomMemberClonerListener : MemberClonerListener + { public override void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) => cloned.Name = $"Method_{original.Name}"; - - public override void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned) => - cloned.Name = $"Property_{original.Name}"; - - public override void OnClonedType(TypeDefinition original, TypeDefinition cloned) => - cloned.Name = $"Type_{original.Name}"; } } From 9bd774702f5911bd5cc5df95db26da25f7e21ce3 Mon Sep 17 00:00:00 2001 From: JPaja Date: Sun, 7 Aug 2022 23:32:05 +0200 Subject: [PATCH 079/182] Make TypeDefOrRefSignature.Type settable --- .../Signatures/Types/TypeDefOrRefSignature.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index cab577dc4..fb989fa00 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -7,6 +7,9 @@ namespace AsmResolver.DotNet.Signatures.Types /// public class TypeDefOrRefSignature : TypeSignature { + private ITypeDefOrRef _type; + private bool _isValueType; + /// /// Creates a new type signature referencing a type in a type metadata table. /// @@ -24,8 +27,7 @@ public TypeDefOrRefSignature(ITypeDefOrRef type) public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType) { Type = type; - IsValueType = isValueType; - + _isValueType = isValueType; } /// @@ -33,7 +35,12 @@ public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType) /// public ITypeDefOrRef Type { - get; + get => _type; + set + { + _type = value; + _isValueType = value.IsValueType; + } } /// @@ -52,10 +59,7 @@ public ITypeDefOrRef Type public override ModuleDefinition? Module => Type.Module; /// - public override bool IsValueType - { - get; - } + public override bool IsValueType => _isValueType; /// public override TypeDefinition? Resolve() => Type.Resolve(); From b3f0444b3eb4a1c84111656e8423b647232c38d8 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 8 Aug 2022 18:36:23 +0200 Subject: [PATCH 080/182] Update xunit deps. --- test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj | 2 +- .../AsmResolver.PE.File.Tests.csproj | 2 +- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 4 ++-- .../AsmResolver.PE.Win32Resources.Tests.csproj | 2 +- .../AsmResolver.Symbols.Pdb.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 5a428afc5..2aa304953 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -10,7 +10,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 ba56b3821..56bcb3259 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 runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index 2e5602e90..17541aa27 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -17,8 +17,8 @@ - - + + 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 c6ae10b54..62839cb1f 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 18d5ed468..3c9d08166 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -9,7 +9,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 82f9cb9c6..d71ccdded 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 80d5d8bf21dbe2cb66b2059404929f2c00c254cb Mon Sep 17 00:00:00 2001 From: CursedLand Date: Mon, 8 Aug 2022 23:09:18 +0200 Subject: [PATCH 081/182] Syntax & Grammer fixs. --- .../Cloning/IMemberClonerListener.cs | 10 +++++----- src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs index 421839b9a..1a6ecaada 100644 --- a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs +++ b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs @@ -12,31 +12,31 @@ public interface IMemberClonerListener /// The cloned member. public void OnClonedMember(IMetadataMember original, IMetadataMember cloned); /// - /// This function is called for every type got cloned. + /// This function is called for every type that got cloned. /// /// The original type. /// The cloned type. public void OnClonedType(TypeDefinition original, TypeDefinition cloned); /// - /// This function is called for every method got cloned. + /// This function is called for every method that got cloned. /// /// The original method. /// The cloned method. public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned); /// - /// This function is called for every field got cloned. + /// This function is called for every field that got cloned. /// /// The original field. /// The cloned field. public void OnClonedField(FieldDefinition original, FieldDefinition cloned); /// - /// This function is called for every property got cloned. + /// This function is called for every property that got cloned. /// /// The original property. /// The cloned property. public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned); /// - /// This function is called for every event got cloned. + /// This function is called for every event that got cloned. /// /// The original event. /// The cloned event. diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs index 6f5531fed..49bea854a 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs @@ -1,7 +1,8 @@ namespace AsmResolver.DotNet.Cloning { /// - public abstract class MemberClonerListener : IMemberClonerListener { + public abstract class MemberClonerListener : IMemberClonerListener + { /// public virtual void OnClonedMember(IMetadataMember original, IMetadataMember cloned) { } /// From b78c24403003716c05c0b8d236c2d946d5602719 Mon Sep 17 00:00:00 2001 From: JPaja Date: Tue, 9 Aug 2022 23:35:36 +0200 Subject: [PATCH 082/182] Fix: GenericType setter --- .../Types/GenericInstanceTypeSignature.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index e05310b12..443854bd1 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -11,6 +11,8 @@ namespace AsmResolver.DotNet.Signatures.Types public class GenericInstanceTypeSignature : TypeSignature, IGenericArgumentsProvider { private readonly List _typeArguments; + private ITypeDefOrRef _genericType; + private bool _isValueType; internal new static GenericInstanceTypeSignature FromReader(in BlobReadContext context, ref BinaryStreamReader reader) { @@ -57,7 +59,7 @@ public GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType) { GenericType = genericType; _typeArguments = new List(typeArguments); - IsValueType = isValueType; + _isValueType = isValueType; } /// @@ -68,8 +70,12 @@ public GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType) /// public ITypeDefOrRef GenericType { - get; - set; + get => _genericType; + set + { + _genericType = value; + _isValueType = _genericType.IsValueType; + } } /// @@ -97,10 +103,7 @@ public ITypeDefOrRef GenericType public override ModuleDefinition? Module => GenericType.Module; /// - public override bool IsValueType - { - get; - } + public override bool IsValueType => _isValueType; /// public override TypeDefinition? Resolve() => GenericType.Resolve(); From 22f7b1e2fe79b3b9a1d795931787ba1695234e5b Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 10 Aug 2022 14:07:51 +0200 Subject: [PATCH 083/182] Add float/double read support in CodeViewLeaf.ReadNumeric. --- src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 5dece241a..bd79027e2 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -89,6 +89,8 @@ internal static object ReadNumeric(ref BinaryStreamReader reader) ULong => reader.ReadUInt32(), QuadWord => reader.ReadInt64(), UQuadWord => reader.ReadUInt64(), + Real32 => reader.ReadSingle(), + Real64 => reader.ReadDouble(), _ => 0 }; } From 4f50531a62cd92b511579fa7fef558eb1bce824b Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 10 Aug 2022 14:46:20 +0200 Subject: [PATCH 084/182] Add read support for LF_VBCLASS and LF_IVBCLASS records. --- .../Leaves/CodeViewLeaf.cs | 5 +- .../SerializedVirtualBaseClassField.cs | 55 +++++++++ .../Leaves/VirtualBaseClassField.cs | 115 ++++++++++++++++++ .../Leaves/FieldListLeafTest.cs | 28 +++++ .../MockPdbFixture.cs | 5 + .../Properties/Resources.Designer.cs | 23 ++-- .../Properties/Resources.resx | 3 + .../Resources/MyTestApplication.pdb | Bin 0 -> 765952 bytes 8 files changed, 224 insertions(+), 10 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVirtualBaseClassField.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/VirtualBaseClassField.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index bd79027e2..60fee5afc 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -58,11 +58,11 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex Class => new SerializedClassTypeRecord(Class, context, typeIndex, dataReader), Enum => new SerializedEnumTypeRecord(context, typeIndex, dataReader), Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), - CodeViewLeafKind.FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader), + FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader), Interface => new SerializedClassTypeRecord(Interface, context, typeIndex, dataReader), Member => new SerializedInstanceDataField(context, typeIndex, ref dataReader), Method => new SerializedOverloadedMethod(context, typeIndex, ref dataReader), - CodeViewLeafKind.MethodList => new SerializedMethodListLeaf(context, typeIndex, dataReader), + MethodList => new SerializedMethodListLeaf(context, typeIndex, dataReader), MFunction => new SerializedMemberFunctionLeaf(context, typeIndex, dataReader), Modifier => new SerializedModifierTypeRecord(context, typeIndex, dataReader), NestType or NestTypeEx => new SerializedNestedTypeField(context, typeIndex, ref dataReader), @@ -72,6 +72,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex Structure => new SerializedClassTypeRecord(Structure, context, typeIndex, dataReader), Union => new SerializedUnionTypeRecord(context, typeIndex, dataReader), VTShape => new SerializedVTableShapeLeaf(context, typeIndex, dataReader), + VBClass or IVBClass => new SerializedVirtualBaseClassField(context, typeIndex, ref dataReader, kind == IVBClass), _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVirtualBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVirtualBaseClassField.cs new file mode 100644 index 000000000..21a9c3322 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVirtualBaseClassField.cs @@ -0,0 +1,55 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVirtualBaseClassField : VirtualBaseClassField +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + private readonly uint _basePointerIndex; + + /// + /// Reads a virtual base class from the provided input stream. + /// + /// The reading context in which the class is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + /// true if the field is an indirect virtual base class, false otherwise. + public SerializedVirtualBaseClassField( + PdbReaderContext context, + uint typeIndex, + ref BinaryStreamReader reader, + bool isIndirect) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _baseTypeIndex = reader.ReadUInt32(); + _basePointerIndex = reader.ReadUInt32(); + PointerOffset = Convert.ToUInt64(ReadNumeric(ref reader)); + TableOffset = Convert.ToUInt64(ReadNumeric(ref reader)); + IsIndirect = isIndirect; + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual base class {TypeIndex:X8} contains an invalid base type index {_baseTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetBasePointerType() + { + return _context.ParentImage.TryGetLeafRecord(_basePointerIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual base class {TypeIndex:X8} contains an invalid base pointer type index {_basePointerIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VirtualBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VirtualBaseClassField.cs new file mode 100644 index 000000000..56e1c4d71 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VirtualBaseClassField.cs @@ -0,0 +1,115 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a direct or indirect reference to a virtual base class object in a structure. +/// +public class VirtualBaseClassField : CodeViewField +{ + private readonly LazyVariable _baseType; + private readonly LazyVariable _basePointerType; + + /// + /// Initializes a new empty virtual base class field. + /// + /// The type index to assign to the field. + protected VirtualBaseClassField(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + _basePointerType = new LazyVariable(GetBasePointerType); + } + + /// + /// Creates a new virtual base class field. + /// + /// The type to reference as base type. + /// The type of the virtual base pointer. + /// The offset of the virtual base pointer + /// The offset from the base table. + /// true if the field is an indirect virtual base class, false otherwise. + public VirtualBaseClassField( + CodeViewTypeRecord baseType, + CodeViewTypeRecord pointerType, + ulong pointerOffset, + ulong tableOffset, + bool isIndirect) + : base(0) + { + _baseType = new LazyVariable(baseType); + _basePointerType = new LazyVariable(pointerType); + PointerOffset = pointerOffset; + TableOffset = tableOffset; + IsIndirect = isIndirect; + } + + /// + /// Gets or sets a value indicating whether the virtual base class is an indirect base class. + /// + public bool IsIndirect + { + get; + set; + } + + /// + public override CodeViewLeafKind LeafKind => IsIndirect + ? CodeViewLeafKind.IVBClass + : CodeViewLeafKind.VBClass; + + /// + /// Gets or sets the base type that this base class is referencing. + /// + public CodeViewTypeRecord? Type + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the type of the base pointer that this base class uses. + /// + public CodeViewTypeRecord? PointerType + { + get => _basePointerType.Value; + set => _basePointerType.Value = value; + } + + /// + /// Gets or sets the virtual base pointer offset relative to the address point. + /// + public ulong PointerOffset + { + get; + set; + } + + /// + /// Gets or sets the virtual base pointer offset relative to the virtual base table. + /// + public ulong TableOffset + { + get; + set; + } + + /// + /// Obtains the base type that the class is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + /// Obtains the type of the base pointer that the class is uses. + /// + /// The base pointer type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBasePointerType() => null; + + /// + public override string ToString() => Type?.ToString() ?? "<<>>"; +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs index 0fad54239..6f8eff36f 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs @@ -79,4 +79,32 @@ public void ReadNestedTypes() Assert.Equal("_LDT_ENTRY::::", Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[2]).Type).Name); } + + [Fact] + public void ReadVirtualBaseClass() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1347); + var baseClass = Assert.IsAssignableFrom(list.Entries[0]); + + Assert.Equal("std::basic_ios >", + Assert.IsAssignableFrom(baseClass.Type).Name); + Assert.True(Assert.IsAssignableFrom(baseClass.PointerType).IsNear64); + Assert.False(baseClass.IsIndirect); + Assert.Equal(0ul, baseClass.PointerOffset); + Assert.Equal(1ul, baseClass.TableOffset); + } + + [Fact] + public void ReadIndirectVirtualBaseClass() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1e97); + var baseClass = Assert.IsAssignableFrom(list.Entries[2]); + + Assert.Equal("std::basic_ios >", + Assert.IsAssignableFrom(baseClass.Type).Name); + Assert.True(Assert.IsAssignableFrom(baseClass.PointerType).IsNear64); + Assert.True(baseClass.IsIndirect); + Assert.Equal(0ul, baseClass.PointerOffset); + Assert.Equal(1ul, baseClass.TableOffset); + } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs b/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs index 63a80f3aa..36218c414 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs @@ -6,4 +6,9 @@ public PdbImage SimplePdb { get; } = PdbImage.FromBytes(Properties.Resources.SimpleDllPdb); + + public PdbImage MyTestApplication + { + get; + } = PdbImage.FromBytes(Properties.Resources.MyTestApplication); } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs index b115b2929..8321cc22d 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs @@ -9,21 +9,21 @@ 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 { @@ -34,7 +34,7 @@ internal class Resources { return resourceMan; } } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Globalization.CultureInfo Culture { get { @@ -44,12 +44,19 @@ internal class Resources { resourceCulture = value; } } - + internal static byte[] SimpleDllPdb { get { object obj = ResourceManager.GetObject("SimpleDllPdb", resourceCulture); return ((byte[])(obj)); } } + + internal static byte[] MyTestApplication { + get { + object obj = ResourceManager.GetObject("MyTestApplication", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx index 64f81d46b..406c6d277 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx +++ b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx @@ -21,4 +21,7 @@ ..\Resources\SimpleDll.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\MyTestApplication.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb b/test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb new file mode 100644 index 0000000000000000000000000000000000000000..abb3d283304bb220b091f09ecd28de082a7869af GIT binary patch literal 765952 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 z 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 z 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 Date: Wed, 10 Aug 2022 14:58:14 +0200 Subject: [PATCH 085/182] Add read support for LF_STMEMBER records. --- .../Leaves/CodeViewDataField.cs | 52 +++++++++++++++++++ .../Leaves/CodeViewLeaf.cs | 1 + .../Leaves/InstanceDataField.cs | 29 ++--------- .../Serialized/SerializedStaticDataField.cs | 45 ++++++++++++++++ .../Leaves/StaticDataField.cs | 29 +++++++++++ .../Leaves/FieldListLeafTest.cs | 12 +++++ 6 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs new file mode 100644 index 000000000..e54d77d21 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs @@ -0,0 +1,52 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +///

+/// Represents a data member in a class or structure type. +/// +public abstract class CodeViewDataField : CodeViewField +{ + private readonly LazyVariable _dataType; + + /// + /// Initializes an empty instance data member. + /// + /// The type index to assign to the member. + protected CodeViewDataField(uint typeIndex) + : base(typeIndex) + { + _dataType = new LazyVariable(GetDataType); + } + + /// + /// Creates a new data member. + /// + /// The data type of the member. + /// The name of the member. + protected CodeViewDataField(CodeViewTypeRecord dataType, Utf8String name) + : base(0) + { + _dataType = new LazyVariable(dataType); + Name = name; + } + + /// + /// Gets or sets the data type of the member. + /// + public CodeViewTypeRecord DataType + { + get => _dataType.Value; + set => _dataType.Value = value; + } + + /// + /// Obtains the data type of the member. + /// + /// The data type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetDataType() => null; + + /// + public override string ToString() => $"{DataType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 60fee5afc..e7dd733d6 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -69,6 +69,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader), Pointer => new SerializedPointerTypeRecord(context, typeIndex, dataReader), Procedure => new SerializedProcedureTypeRecord(context, typeIndex, dataReader), + StMember => new SerializedStaticDataField(context, typeIndex, ref dataReader), Structure => new SerializedClassTypeRecord(Structure, context, typeIndex, dataReader), Union => new SerializedUnionTypeRecord(context, typeIndex, dataReader), VTShape => new SerializedVTableShapeLeaf(context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs index 39ecd208e..aa8b8eeb3 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs @@ -3,10 +3,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents an instance data member in a class or structure type. /// -public class InstanceDataField : CodeViewField +public class InstanceDataField : CodeViewDataField { - private readonly LazyVariable _dataType; - /// /// Initializes an empty instance data member. /// @@ -14,7 +12,6 @@ public class InstanceDataField : CodeViewField protected InstanceDataField(uint typeIndex) : base(typeIndex) { - _dataType = new LazyVariable(GetDataType); } /// @@ -24,25 +21,14 @@ protected InstanceDataField(uint typeIndex) /// The name of the member. /// The byte offset within the class or structure that the member is stored at. public InstanceDataField(CodeViewTypeRecord dataType, Utf8String name, ulong offset) - : base(0) + : base(dataType, name) { - _dataType = new LazyVariable(dataType); - Name = name; Offset = offset; } /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Member; - /// - /// Gets or sets the data type of the member. - /// - public CodeViewTypeRecord DataType - { - get => _dataType.Value; - set => _dataType.Value = value; - } - /// /// Gets or sets the byte offset within the class or structure that the member is stored at. /// @@ -52,15 +38,6 @@ public ulong Offset set; } - /// - /// Obtains the data type of the member. - /// - /// The data type. - /// - /// This method is called upon initialization of the property. - /// - protected virtual CodeViewTypeRecord? GetDataType() => null; - /// - public override string ToString() => $"[{Offset:X4}]: {DataType} {Name}"; + public override string ToString() => $"+{Offset:X4}: {DataType} {Name}"; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs new file mode 100644 index 000000000..4196ac5c9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs @@ -0,0 +1,45 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedStaticDataField : StaticDataField +{ + private readonly PdbReaderContext _context; + private readonly uint _dataTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a static data member list from the provided input stream. + /// + /// The reading context in which the member is situated in. + /// The type index to assign to the member. + /// The input stream to read from. + public SerializedStaticDataField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _dataTypeIndex = reader.ReadUInt32(); + + _context = context; + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetDataType() + { + if (_dataTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Static data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs new file mode 100644 index 000000000..22ea56e56 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs @@ -0,0 +1,29 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a static data member in a class or structure type. +/// +public class StaticDataField : CodeViewDataField +{ + /// + /// Initializes an empty static data field. + /// + /// The type index to assign to the field. + protected StaticDataField(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new static data member. + /// + /// The data type of the member. + /// The name. + public StaticDataField(CodeViewTypeRecord dataType, Utf8String name) + : base(dataType, name) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.StMember; +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs index 6f8eff36f..a50d179b8 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs @@ -107,4 +107,16 @@ public void ReadIndirectVirtualBaseClass() Assert.Equal(0ul, baseClass.PointerOffset); Assert.Equal(1ul, baseClass.TableOffset); } + + [Fact] + public void ReadStaticFields() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1423); + + Assert.Equal("is_bounded", Assert.IsAssignableFrom(list.Entries[1]).Name); + Assert.Equal("is_exact", Assert.IsAssignableFrom(list.Entries[2]).Name); + Assert.Equal("is_integer", Assert.IsAssignableFrom(list.Entries[3]).Name); + Assert.Equal("is_specialized", Assert.IsAssignableFrom(list.Entries[4]).Name); + Assert.Equal("radix", Assert.IsAssignableFrom(list.Entries[5]).Name); + } } From e69c062a54ff58da61c3c8ae5ddaf7cd5f5fb5e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Aug 2022 14:27:52 +0000 Subject: [PATCH 086/182] Bump Microsoft.NET.Test.Sdk from 17.2.0 to 17.3.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.2.0 to 17.3.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.2.0...v17.3.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 +- .../AsmResolver.Symbols.Pdb.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 2aa304953..48c9e7ed2 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 56bcb3259..e38058430 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 17541aa27..b9f309112 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 62839cb1f..efbdd2b45 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.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 3c9d08166..ff08cc804 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -8,7 +8,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index fd465084a..890b3b276 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,7 +8,7 @@ - + all From c6bb816a5c25861afc790f4aeb1ca05afb25291e Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 10 Aug 2022 22:21:30 +0200 Subject: [PATCH 087/182] Add read support for LF_BITFIELD records. --- .../Leaves/BitFieldTypeRecord.cs | 75 +++++++++++++++++++ .../Leaves/CodeViewLeaf.cs | 1 + .../SerializedBitFieldTypeRecord.cs | 36 +++++++++ .../Leaves/ArrayTypeRecordTest.cs | 2 +- .../Leaves/BitFieldTypeRecordTest.cs | 35 +++++++++ 5 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs new file mode 100644 index 000000000..40a01fbb5 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs @@ -0,0 +1,75 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a bit field type. +/// +public class BitFieldTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty bit field record. + /// + /// The type index to assign to the bit field type. + protected BitFieldTypeRecord(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new bit field record. + /// + /// The type of the bit field. + /// The bit index the bit field starts at. + /// The number of bits the bit field spans. + public BitFieldTypeRecord(CodeViewTypeRecord type, byte position, byte length) + : base(0) + { + _type = new LazyVariable(type); + Position = position; + Length = length; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BitField; + + /// + /// Gets or sets the base type that this bit field is referencing. + /// + public CodeViewTypeRecord? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the bit index that this bit fields starts at. + /// + public byte Position + { + get; + set; + } + + /// + /// Gets or sets the number of bits that this bit fields spans. + /// + public byte Length + { + get; + set; + } + + /// + /// Obtains the base type that the bit field is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => $"{Type} : {Length}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index e7dd733d6..322558e55 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -56,6 +56,7 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex ArgList => new SerializedArgumentListLeaf(context, typeIndex, dataReader), BClass => new SerializedBaseClassField(context, typeIndex, ref dataReader), Class => new SerializedClassTypeRecord(Class, context, typeIndex, dataReader), + BitField => new SerializedBitFieldTypeRecord(context, typeIndex, dataReader), Enum => new SerializedEnumTypeRecord(context, typeIndex, dataReader), Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs new file mode 100644 index 000000000..e18e87dfe --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs @@ -0,0 +1,36 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedBitFieldTypeRecord : BitFieldTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a bit field type from the provided input stream. + /// + /// The reading context in which the bit field type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedBitFieldTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Length = reader.ReadByte(); + Position = reader.ReadByte(); + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Bit field type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs index f22d763c2..b33c90b37 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Tests.Leaves; -public class ArrayTypeRecordTest: IClassFixture +public class ArrayTypeRecordTest : IClassFixture { private readonly MockPdbFixture _fixture; diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs new file mode 100644 index 000000000..34902397f --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs @@ -0,0 +1,35 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class BitFieldTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public BitFieldTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadBaseType() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(SimpleTypeKind.UInt32Long, Assert.IsAssignableFrom(type.Type).Kind); + } + + [Fact] + public void ReadPosition() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(2, type.Position); + } + + [Fact] + public void ReadLength() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(30, type.Length); + } +} From 528c663a822d6a9ef58552136bd329f0bcbdf8b7 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 10 Aug 2022 22:41:45 +0200 Subject: [PATCH 088/182] Add read support for LF_VFUNCTAB records. --- .../Leaves/CodeViewDataField.cs | 2 +- .../Leaves/CodeViewField.cs | 24 --------- .../Leaves/CodeViewLeaf.cs | 7 ++- .../Leaves/CodeViewNamedField.cs | 40 +++++++++++++++ .../Leaves/EnumerateField.cs | 2 +- .../Leaves/NestedTypeField.cs | 2 +- .../Leaves/NonOverloadedMethod.cs | 10 ++-- .../Leaves/OverloadedMethod.cs | 2 +- .../SerializedNonOverloadedMethod.cs | 2 +- ...sField.cs => SerializedVBaseClassField.cs} | 6 +-- .../Serialized/SerializedVTableField.cs | 35 +++++++++++++ ...alBaseClassField.cs => VBaseClassField.cs} | 6 +-- .../Leaves/VTableField.cs | 50 +++++++++++++++++++ .../Leaves/FieldListLeafTest.cs | 12 ++++- 14 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs rename src/AsmResolver.Symbols.Pdb/Leaves/Serialized/{SerializedVirtualBaseClassField.cs => SerializedVBaseClassField.cs} (92%) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs rename src/AsmResolver.Symbols.Pdb/Leaves/{VirtualBaseClassField.cs => VBaseClassField.cs} (96%) create mode 100644 src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs index e54d77d21..3821a14fe 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a data member in a class or structure type. /// -public abstract class CodeViewDataField : CodeViewField +public abstract class CodeViewDataField : CodeViewNamedField { private readonly LazyVariable _dataType; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs index 141023404..689105e37 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -5,8 +5,6 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public abstract class CodeViewField : CodeViewLeaf { - private readonly LazyVariable _name; - /// /// Initializes an empty CodeView field leaf. /// @@ -14,16 +12,6 @@ public abstract class CodeViewField : CodeViewLeaf protected CodeViewField(uint typeIndex) : base(typeIndex) { - _name = new LazyVariable(GetName); - } - - /// - /// Gets or sets the name of the field. - /// - public Utf8String Name - { - get => _name.Value; - set => _name.Value = value; } /// @@ -41,16 +29,4 @@ public CodeViewFieldAttributes Attributes public bool IsIntroducingVirtual => (Attributes & CodeViewFieldAttributes.IntroducingVirtual) == CodeViewFieldAttributes.IntroducingVirtual || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) == CodeViewFieldAttributes.PureIntroducingVirtual; - - /// - /// Obtains the name of the field. - /// - /// The name. - /// - /// This method is called upon initialization of the property. - /// - protected virtual Utf8String GetName() => Utf8String.Empty; - - /// - public override string ToString() => Name; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index 322558e55..c966aa91e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -55,12 +55,11 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex Array => new SerializedArrayTypeRecord(context, typeIndex, dataReader), ArgList => new SerializedArgumentListLeaf(context, typeIndex, dataReader), BClass => new SerializedBaseClassField(context, typeIndex, ref dataReader), - Class => new SerializedClassTypeRecord(Class, context, typeIndex, dataReader), + Class or Interface or Structure => new SerializedClassTypeRecord(kind, context, typeIndex, dataReader), BitField => new SerializedBitFieldTypeRecord(context, typeIndex, dataReader), Enum => new SerializedEnumTypeRecord(context, typeIndex, dataReader), Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader), - Interface => new SerializedClassTypeRecord(Interface, context, typeIndex, dataReader), Member => new SerializedInstanceDataField(context, typeIndex, ref dataReader), Method => new SerializedOverloadedMethod(context, typeIndex, ref dataReader), MethodList => new SerializedMethodListLeaf(context, typeIndex, dataReader), @@ -71,10 +70,10 @@ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex Pointer => new SerializedPointerTypeRecord(context, typeIndex, dataReader), Procedure => new SerializedProcedureTypeRecord(context, typeIndex, dataReader), StMember => new SerializedStaticDataField(context, typeIndex, ref dataReader), - Structure => new SerializedClassTypeRecord(Structure, context, typeIndex, dataReader), Union => new SerializedUnionTypeRecord(context, typeIndex, dataReader), + VFuncTab => new SerializedVTableField(context, typeIndex, ref dataReader), VTShape => new SerializedVTableShapeLeaf(context, typeIndex, dataReader), - VBClass or IVBClass => new SerializedVirtualBaseClassField(context, typeIndex, ref dataReader, kind == IVBClass), + VBClass or IVBClass => new SerializedVBaseClassField(context, typeIndex, ref dataReader, kind == IVBClass), _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) }; } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs new file mode 100644 index 000000000..3137dabde --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs @@ -0,0 +1,40 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single record in a field list that is assigned a name. +/// +public abstract class CodeViewNamedField : CodeViewField +{ + private readonly LazyVariable _name; + + /// + /// Initializes an empty CodeView field leaf. + /// + /// The type index to assign to the leaf. + protected CodeViewNamedField(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + } + + /// + /// Gets or sets the name of the field. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the field. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => Name; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs index c14ecb122..4fe1a97c9 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a single enumerate field leaf in a field list. /// -public class EnumerateField : CodeViewField +public class EnumerateField : CodeViewNamedField { private readonly LazyVariable _value; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs index 0f6236113..91bca6ee3 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a field in a type that references a nested type definition. /// -public class NestedTypeField : CodeViewField +public class NestedTypeField : CodeViewNamedField { private readonly LazyVariable _type; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs index 06d079c27..01e655c3f 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a single method in a type. /// -public class NonOverloadedMethod : CodeViewField +public class NonOverloadedMethod : CodeViewNamedField { private readonly LazyVariable _function; @@ -37,14 +37,14 @@ public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes /// The function that is referenced by the method. /// The attributes associated to the method. /// The name of the method. - /// The offset to the slot the virtual function table that this method occupies. - public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes attributes, Utf8String name, uint vfTableOffset) + /// The offset to the slot the virtual function table that this method occupies. + public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes attributes, Utf8String name, uint vTableOffset) : base(0) { _function = new LazyVariable(function); Attributes = attributes; Name = name; - VFTableOffset = vfTableOffset; + VTableOffset = vTableOffset; } /// @@ -63,7 +63,7 @@ public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function /// table that this method occupies. /// - public uint VFTableOffset + public uint VTableOffset { get; set; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs index 20d1b3fda..33376c86f 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a method that is overloaded by one or more functions. /// -public class OverloadedMethod : CodeViewField +public class OverloadedMethod : CodeViewNamedField { private readonly LazyVariable _methods; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs index 96f10b517..0d9cc45ef 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs @@ -24,7 +24,7 @@ public SerializedNonOverloadedMethod(PdbReaderContext context, uint typeIndex, r Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); _functionIndex = reader.ReadUInt32(); if (IsIntroducingVirtual) - VFTableOffset = reader.ReadUInt32(); + VTableOffset = reader.ReadUInt32(); _nameReader = reader.Fork(); reader.AdvanceUntil(0, true); } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVirtualBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs similarity index 92% rename from src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVirtualBaseClassField.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs index 21a9c3322..5be9b1165 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVirtualBaseClassField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs @@ -4,9 +4,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; /// -/// Provides a lazily initialized implementation of that is read from a PDB image. +/// Provides a lazily initialized implementation of that is read from a PDB image. /// -public class SerializedVirtualBaseClassField : VirtualBaseClassField +public class SerializedVBaseClassField : VBaseClassField { private readonly PdbReaderContext _context; private readonly uint _baseTypeIndex; @@ -19,7 +19,7 @@ public class SerializedVirtualBaseClassField : VirtualBaseClassField /// The type index to assign to the type. /// The input stream to read from. /// true if the field is an indirect virtual base class, false otherwise. - public SerializedVirtualBaseClassField( + public SerializedVBaseClassField( PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader, diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs new file mode 100644 index 000000000..66ed003c7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVTableField : VTableField +{ + private readonly PdbReaderContext _context; + private readonly uint _pointerTypeIndex; + + /// + /// Reads a virtual function table field from the provided input stream. + /// + /// The reading context in which the field is situated in. + /// The type index to assign to the field. + /// The input stream to read from. + public SerializedVTableField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + reader.ReadUInt16(); // padding + _pointerTypeIndex = reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetPointerType() + { + return _context.ParentImage.TryGetLeafRecord(_pointerTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual function table type {TypeIndex:X8} contains an invalid pointer type index {_pointerTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VirtualBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs similarity index 96% rename from src/AsmResolver.Symbols.Pdb/Leaves/VirtualBaseClassField.cs rename to src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs index 56e1c4d71..99d5edf0e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/VirtualBaseClassField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a direct or indirect reference to a virtual base class object in a structure. /// -public class VirtualBaseClassField : CodeViewField +public class VBaseClassField : CodeViewField { private readonly LazyVariable _baseType; private readonly LazyVariable _basePointerType; @@ -12,7 +12,7 @@ public class VirtualBaseClassField : CodeViewField /// Initializes a new empty virtual base class field. /// /// The type index to assign to the field. - protected VirtualBaseClassField(uint typeIndex) + protected VBaseClassField(uint typeIndex) : base(typeIndex) { _baseType = new LazyVariable(GetBaseType); @@ -27,7 +27,7 @@ protected VirtualBaseClassField(uint typeIndex) /// The offset of the virtual base pointer /// The offset from the base table. /// true if the field is an indirect virtual base class, false otherwise. - public VirtualBaseClassField( + public VBaseClassField( CodeViewTypeRecord baseType, CodeViewTypeRecord pointerType, ulong pointerOffset, diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs new file mode 100644 index 000000000..11355cad4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs @@ -0,0 +1,50 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents the virtual function table field in a class. +/// +public class VTableField : CodeViewField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty virtual function table field. + /// + /// The type index to assign to the type. + protected VTableField(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetPointerType); + } + + /// + /// Creates a new virtual function table field. + /// + /// The pointer type to use. + public VTableField(CodeViewTypeRecord pointerType) + : base(0) + { + _type = new LazyVariable(pointerType); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.VFuncTab; + + /// + /// Gets or sets the pointer type of the virtual function table. + /// + public CodeViewTypeRecord? PointerType + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Obtains the pointer type that the virtual function table type. + /// + /// The pointer type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetPointerType() => null; +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs index a50d179b8..cd067940b 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs @@ -84,7 +84,7 @@ public void ReadNestedTypes() public void ReadVirtualBaseClass() { var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1347); - var baseClass = Assert.IsAssignableFrom(list.Entries[0]); + var baseClass = Assert.IsAssignableFrom(list.Entries[0]); Assert.Equal("std::basic_ios >", Assert.IsAssignableFrom(baseClass.Type).Name); @@ -98,7 +98,7 @@ public void ReadVirtualBaseClass() public void ReadIndirectVirtualBaseClass() { var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1e97); - var baseClass = Assert.IsAssignableFrom(list.Entries[2]); + var baseClass = Assert.IsAssignableFrom(list.Entries[2]); Assert.Equal("std::basic_ios >", Assert.IsAssignableFrom(baseClass.Type).Name); @@ -119,4 +119,12 @@ public void ReadStaticFields() Assert.Equal("is_specialized", Assert.IsAssignableFrom(list.Entries[4]).Name); Assert.Equal("radix", Assert.IsAssignableFrom(list.Entries[5]).Name); } + + [Fact] + public void ReadVTableField() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1215); + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[0]).PointerType); + Assert.IsAssignableFrom(list.Entries[1]); + } } From a72dcd9692bda3354e0dd0ac34d7b182fb44f937 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 10 Aug 2022 23:10:45 +0200 Subject: [PATCH 089/182] Add missing GetFunction override in LF_ONEMETHOD records. --- .../Leaves/Serialized/SerializedMethodListEntry.cs | 2 +- .../Leaves/Serialized/SerializedNonOverloadedMethod.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs index a3994042e..938779468 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs @@ -30,6 +30,6 @@ public SerializedMethodListEntry(PdbReaderContext context, ref BinaryStreamReade return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type ? type : _context.Parameters.ErrorListener.BadImageAndReturn( - $"Method list entry contains an invalid return type index {_functionIndex:X8}."); + $"Method list entry contains an invalid function type index {_functionIndex:X8}."); } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs index 0d9cc45ef..08347ed9d 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs @@ -31,4 +31,13 @@ public SerializedNonOverloadedMethod(PdbReaderContext context, uint typeIndex, r /// protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override MemberFunctionLeaf? GetFunction() + { + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Method {TypeIndex:X8} contains an invalid function type index {_functionIndex:X8}."); + } } From 76f55a8d256fa0e37d24cc54447725dc6265c427 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:02:13 +0200 Subject: [PATCH 090/182] Rename TypeRecordReader to LeafRecordReader in TPI stream. --- .../Metadata/Tpi/SerializedTpiStream.cs | 2 +- .../Metadata/Tpi/TpiStream.cs | 10 +++++----- .../SerializedPdbImage.cs | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs index 16bdb06a0..3a4c144c7 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs @@ -70,7 +70,7 @@ private List<(uint Offset, uint Length)> GetRecordOffsets() } /// - public override bool TryGetTypeRecordReader(uint typeIndex, out BinaryStreamReader reader) + public override bool TryGetLeafRecordReader(uint typeIndex, out BinaryStreamReader reader) { EnsureRecordOffsetMappingInitialized(); diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs index 5a4563d32..f3a5440e3 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs @@ -172,7 +172,7 @@ public uint HashAdjBufferLength public static TpiStream FromReader(BinaryStreamReader reader) => new SerializedTpiStream(reader); /// - /// Attempts to get a reader object that starts at the beginning of a type record for the provided type index. + /// Attempts to get a reader object that starts at the beginning of a leaf record for the provided type index. /// /// The type index to get the reader for. /// The obtained reader object. @@ -180,17 +180,17 @@ public uint HashAdjBufferLength /// true if the provided type index was valid and a reader object was constructed successfully, /// false otherwise. /// - public abstract bool TryGetTypeRecordReader(uint typeIndex, out BinaryStreamReader reader); + public abstract bool TryGetLeafRecordReader(uint typeIndex, out BinaryStreamReader reader); /// - /// Gets a reader object that starts at the beginning of a type record for the provided type index. + /// Gets a reader object that starts at the beginning of a leaf record for the provided type index. /// /// The type index to get the reader for. /// The obtained reader object. /// Occurs when the provided type index was invalid. - public BinaryStreamReader GetTypeRecordReader(uint typeIndex) + public BinaryStreamReader GetLeafRecordReader(uint typeIndex) { - if (!TryGetTypeRecordReader(typeIndex, out var reader)) + if (!TryGetLeafRecordReader(typeIndex, out var reader)) throw new ArgumentException("Invalid type index."); return reader; diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index 38cf32db9..a12d26551 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -19,7 +19,7 @@ public class SerializedPdbImage : PdbImage { private const int MinimalRequiredStreamCount = 5; private readonly MsfFile _file; - private CodeViewLeaf?[]? _types; + private CodeViewLeaf?[]? _leaves; /// /// Interprets a PDB image from the provided MSF file. @@ -60,12 +60,12 @@ internal TpiStream TpiStream get; } - [MemberNotNull(nameof(_types))] + [MemberNotNull(nameof(_leaves))] private void EnsureTypeArrayInitialized() { - if (_types is null) + if (_leaves is null) { - Interlocked.CompareExchange(ref _types, + Interlocked.CompareExchange(ref _leaves, new CodeViewLeaf?[TpiStream.TypeIndexEnd - TpiStream.TypeIndexBegin], null); } } @@ -80,14 +80,14 @@ public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out Co if (typeIndex >= TpiStream.TypeIndexBegin && typeIndex < TpiStream.TypeIndexEnd) { - type = _types[typeIndex - TpiStream.TypeIndexBegin]; - if (type is null && TpiStream.TryGetTypeRecordReader(typeIndex, out var reader)) + type = _leaves[typeIndex - TpiStream.TypeIndexBegin]; + if (type is null && TpiStream.TryGetLeafRecordReader(typeIndex, out var reader)) { type = CodeViewLeaf.FromReader(ReaderContext, typeIndex, ref reader); - Interlocked.CompareExchange(ref _types[typeIndex - TpiStream.TypeIndexBegin], type, null); + Interlocked.CompareExchange(ref _leaves[typeIndex - TpiStream.TypeIndexBegin], type, null); } - type = _types[typeIndex - TpiStream.TypeIndexBegin]; + type = _leaves[typeIndex - TpiStream.TypeIndexBegin]; return type is not null; } From 7b3379a610bdab172e2d0cc2c6c8dcb48772e69b Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:03:20 +0200 Subject: [PATCH 091/182] Add BaseClassField::.ctor --- src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs index 355a5be80..3a3823945 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs @@ -17,6 +17,16 @@ protected BaseClassField(uint typeIndex) _type = new LazyVariable(GetBaseType); } + /// + /// Creates a new base class field. + /// + /// The base type to reference. + public BaseClassField(CodeViewTypeRecord type) + : base(0) + { + _type = new LazyVariable(type); + } + /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BClass; From b1d549c0c59f0b1ca11f7acc3b28fec4cba35a9a Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:18:01 +0200 Subject: [PATCH 092/182] Update xmldoc in calling conventions. --- .../Leaves/CodeViewCallingConvention.cs | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs index aacdc00f3..e17d8f881 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs @@ -6,127 +6,127 @@ namespace AsmResolver.Symbols.Pdb.Leaves; public enum CodeViewCallingConvention : byte { /// - /// near right to left push, caller pops stack + /// Indicates a near call using the cdecl calling convention. /// NearC = 0x00, /// - /// far right to left push, caller pops stack + /// Indicates a far call using the cdecl calling convention. /// FarC = 0x01, /// - /// near left to right push, callee pops stack + /// Indicates a near call using the Pascal calling convention. /// NearPascal = 0x02, /// - /// far left to right push, callee pops stack + /// Indicates a far call using the Pascal calling convention. /// FarPascal = 0x03, /// - /// near left to right push with regs, callee pops stack + /// Indicates a near call using the fastcall calling convention. /// NearFast = 0x04, /// - /// far left to right push with regs, callee pops stack + /// Indicates a far call using the fastcall calling convention. /// FarFast = 0x05, /// - /// skipped (unused) call index + /// Skipped (unused) call index /// Skipped = 0x06, /// - /// near standard call + /// Indicates a near call using the stdcall calling convention. /// NearStd = 0x07, /// - /// far standard call + /// Indicates a far call using the stdcall calling convention. /// FarStd = 0x08, /// - /// near sys call + /// Indicates a near call using the syscall calling convention. /// NearSys = 0x09, /// - /// far sys call + /// Indicates a far call using the syscall calling convention. /// FarSys = 0x0a, /// - /// this call (this passed in register) + /// Indicates a call using the thiscall calling convention. /// ThisCall = 0x0b, /// - /// Mips call + /// Indicates a call using the MIPS calling convention. /// MipsCall = 0x0c, /// - /// Generic call sequence + /// Indicates a generic calling sequence. /// Generic = 0x0d, /// - /// Alpha call + /// Indicates a call using the Alpha calling convention. /// AlphaCall = 0x0e, /// - /// PPC call + /// Indicates a call using the PowerPC calling convention. /// PpcCall = 0x0f, /// - /// Hitachi SuperH call + /// Indicates a call using the Hitachi SuperH calling convention. /// ShCall = 0x10, /// - /// ARM call + /// Indicates a call using the ARM calling convention. /// ArmCall = 0x11, /// - /// AM33 call + /// Indicates a call using the AM33 calling convention. /// Am33Call = 0x12, /// - /// TriCore Call + /// Indicates a call using the TriCore calling convention. /// TriCall = 0x13, /// - /// Hitachi SuperH-5 call + /// Indicates a call using the Hitachi SuperH-5 calling convention. /// Sh5Call = 0x14, /// - /// M32R Call + /// Indicates a call using the M32R calling convention. /// M32RCall = 0x15, /// - /// clr call + /// Indicates a call using the clr calling convention. /// ClrCall = 0x16, /// - /// Marker for routines always inlined and thus lacking a convention + /// Marker for routines always inlined and thus lacking a convention. /// Inline = 0x17, /// - /// near left to right push with regs, callee pops stack + /// Indicates a near call using the vectorcall calling convention. /// NearVector = 0x18, } From d2afd6f292cefcd1fbbdf00c1dad0f3805d8b409 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:20:33 +0200 Subject: [PATCH 093/182] Clean up leftover microsoft-pdb comments in leafkind. --- .../Leaves/CodeViewLeafKind.cs | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs index a194e4b05..6acb79d56 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs @@ -5,12 +5,12 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public enum CodeViewLeafKind : ushort { -#pragma warning disable CS1591 /// /// This is not really a type defined in PDB spec, but is used to indicate simple types. /// SimpleType = 0xffff, +#pragma warning disable CS1591 Modifier16T = 0x0001, Pointer16T = 0x0002, Array16T = 0x0003, @@ -29,12 +29,10 @@ public enum CodeViewLeafKind : ushort Nottran = 0x0010, DimArray16T = 0x0011, VftPath16T = 0x0012, - PreComp16T = 0x0013, // not referenced from symbol - EndPreComp = 0x0014, // not referenced from symbol - Oem16T = 0x0015, // oem definable type string - TypeServerSt = 0x0016, // not referenced from symbol - - // leaf indices starting records but referenced only from type records + PreComp16T = 0x0013, + EndPreComp = 0x0014, + Oem16T = 0x0015, + TypeServerSt = 0x0016, Skip16T = 0x0200, ArgList16T = 0x0201, @@ -65,8 +63,6 @@ public enum CodeViewLeafKind : ushort OneMethod16T = 0x040c, VFuncOff16T = 0x040d, -// 32-bit type index versions of leaves, all have the 0x1000 bit set -// Ti16Max = 0x1000, Modifier = 0x1001, @@ -82,12 +78,10 @@ public enum CodeViewLeafKind : ushort BArray = 0x100b, DimArraySt = 0x100c, VftPath = 0x100d, - PreCompSt = 0x100e, // not referenced from symbol - Oem = 0x100f, // oem definable type string - AliasSt = 0x1010, // alias (typedef) type - Oem2 = 0x1011, // oem definable type string - - // leaf indices starting records but referenced only from type records + PreCompSt = 0x100e, + Oem = 0x100f, + AliasSt = 0x1010, + Oem2 = 0x1011, Skip = 0x1200, ArgList = 0x1201, @@ -118,11 +112,9 @@ public enum CodeViewLeafKind : ushort MemberModifySt = 0x140e, ManagedSt = 0x140f, - // Types w/ SZ names - StMax = 0x1500, - TypeServer = 0x1501, // not referenced from symbol + TypeServer = 0x1501, Enumerate = 0x1502, Array = 0x1503, Class = 0x1504, @@ -130,8 +122,8 @@ public enum CodeViewLeafKind : ushort Union = 0x1506, Enum = 0x1507, DimArray = 0x1508, - PreComp = 0x1509, // not referenced from symbol - Alias = 0x150a, // alias (typedef) type + PreComp = 0x1509, + Alias = 0x150a, DefArg = 0x150b, FriendFcn = 0x150c, Member = 0x150d, @@ -144,7 +136,7 @@ public enum CodeViewLeafKind : ushort Managed = 0x1514, TypeServer2 = 0x1515, - StridedArray = 0x1516, // same as ARRAY, but with stride between adjacent elements + StridedArray = 0x1516, Hlsl = 0x1517, ModifierEx = 0x1518, Interface = 0x1519, @@ -152,25 +144,22 @@ public enum CodeViewLeafKind : ushort Vector = 0x151b, Matrix = 0x151c, - VFTable = 0x151d, // a virtual function table + VFTable = 0x151d, EndOfLeafRecord = VFTable, - TypeLast, // one greater than the last type record + TypeLast, TypeMax = TypeLast - 1, - FuncId = 0x1601, // global func ID - MFuncId = 0x1602, // member func ID - Buildinfo = 0x1603, // build info: tool, version, command line, src/pdb file - SubstrList = 0x1604, // similar to ARGLIST, for list of sub strings - StringId = 0x1605, // string ID - - UdtSrcLine = 0x1606, // source and line on where an UDT is defined - // only generated by compiler + FuncId = 0x1601, + MFuncId = 0x1602, + Buildinfo = 0x1603, + SubstrList = 0x1604, + StringId = 0x1605, - UdtModSrcLine = 0x1607, // module, source and line on where an UDT is defined - // only generated by linker + UdtSrcLine = 0x1606, + UdtModSrcLine = 0x1607, - IdLast, // one greater than the last ID record + IdLast, IdMax = IdLast - 1, Numeric = 0x8000, From 53834b26eda735fcf909dbed2080636cfa2ba36f Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:26:30 +0200 Subject: [PATCH 094/182] Add missing MemberFunctionLeaf::.ctor --- .../Leaves/MemberFunctionLeaf.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs index 520747643..5069796fb 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs @@ -25,6 +25,24 @@ protected MemberFunctionLeaf(uint typeIndex) _argumentList = new LazyVariable(GetArguments); } + /// + /// Creates a new member function. + /// + /// The return type of the function. + /// The declaring type of the function. + /// The argument types of the function. + public MemberFunctionLeaf(CodeViewTypeRecord returnType, CodeViewTypeRecord declaringType, ArgumentListLeaf arguments) + : base(0) + { + _returnType = new LazyVariable(returnType); + _declaringType = new LazyVariable(declaringType); + _thisType = new LazyVariable(default(CodeViewTypeRecord)); + _argumentList = new LazyVariable(arguments); + CallingConvention = CodeViewCallingConvention.NearC; + Attributes = 0; + ThisAdjuster = 0; + } + /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MFunction; From 1764c50eba54146833aa2294da2a72478221a05b Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:27:06 +0200 Subject: [PATCH 095/182] Rename MethodListEntry::VFTableOffset to VTableOffset. --- .../Leaves/MethodListEntry.cs | 12 ++++++------ .../Leaves/Serialized/SerializedMethodListEntry.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs index 73fbc906e..447296798 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs @@ -24,7 +24,7 @@ public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf fu { Attributes = attributes; _function = new LazyVariable(function); - VFTableOffset = 0; + VTableOffset = 0; } /// @@ -32,12 +32,12 @@ public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf fu /// /// The attributes associated to this method. /// The referenced function. - /// The offset to the slot the virtual function table that this method occupies. - public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function, uint vfTableOffset) + /// The offset to the slot the virtual function table that this method occupies. + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function, uint vTableOffset) { Attributes = attributes; _function = new LazyVariable(function); - VFTableOffset = vfTableOffset; + VTableOffset = vTableOffset; } /// @@ -69,7 +69,7 @@ public CodeViewFieldAttributes Attributes /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function /// table that this method occupies. /// - public uint VFTableOffset + public uint VTableOffset { get; set; @@ -88,7 +88,7 @@ public uint VFTableOffset public override string ToString() { return IsIntroducingVirtual - ? $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}, {nameof(VFTableOffset)}: {VFTableOffset}" + ? $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}, {nameof(VTableOffset)}: {VTableOffset}" : $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}"; } } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs index 938779468..af17da782 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs @@ -21,7 +21,7 @@ public SerializedMethodListEntry(PdbReaderContext context, ref BinaryStreamReade Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); reader.ReadUInt16(); // padding _functionIndex = reader.ReadUInt32(); - VFTableOffset = IsIntroducingVirtual ? reader.ReadUInt32() : 0; + VTableOffset = IsIntroducingVirtual ? reader.ReadUInt32() : 0; } /// From 63449413d9d09b0d25a710a5b87fce5b4821f935 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:31:09 +0200 Subject: [PATCH 096/182] Add public constructors for methods. --- .../Leaves/MethodListLeaf.cs | 11 ++++++++++ .../Leaves/NonOverloadedMethod.cs | 12 +++++------ .../Leaves/OverloadedMethod.cs | 21 ++++++++++++++++++- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs index 77756447d..f9538bac3 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading; namespace AsmResolver.Symbols.Pdb.Leaves; @@ -27,6 +28,16 @@ public MethodListLeaf() { } + /// + /// Creates a new method list. + /// + /// The methods to include. + public MethodListLeaf(params MethodListEntry[] entries) + : base(0) + { + _entries = entries.ToList(); + } + /// public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MethodList; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs index 01e655c3f..08bdb113f 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs @@ -20,10 +20,10 @@ protected NonOverloadedMethod(uint typeIndex) /// /// Creates a new overloaded method. /// - /// The function that is referenced by the method. - /// The attributes associated to the method. /// The name of the method. - public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes attributes, Utf8String name) + /// The attributes associated to the method. + /// The function that is referenced by the method. + public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, MemberFunctionLeaf function) : base(0) { _function = new LazyVariable(function); @@ -34,11 +34,11 @@ public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes /// /// Creates a new overloaded method. /// - /// The function that is referenced by the method. - /// The attributes associated to the method. /// The name of the method. + /// The attributes associated to the method. /// The offset to the slot the virtual function table that this method occupies. - public NonOverloadedMethod(MemberFunctionLeaf function, CodeViewFieldAttributes attributes, Utf8String name, uint vTableOffset) + /// The function that is referenced by the method. + public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, uint vTableOffset, MemberFunctionLeaf function) : base(0) { _function = new LazyVariable(function); diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs index 33376c86f..a6c17cea0 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs @@ -21,8 +21,27 @@ protected OverloadedMethod(uint typeIndex) /// Creates a new empty overloaded method. /// public OverloadedMethod() - : this(0) + : base(0) { + _methods = new LazyVariable(new MethodListLeaf()); + } + + /// + /// Creates a new overloaded method. + /// + public OverloadedMethod(MethodListLeaf methods) + : base(0) + { + _methods = new LazyVariable(methods); + } + + /// + /// Creates a new overloaded method. + /// + public OverloadedMethod(params MethodListEntry[] methods) + : base(0) + { + _methods = new LazyVariable(new MethodListLeaf(methods)); } /// From 59e021f4e3c24dae82eecd74fb5d45d49a8b3170 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:36:20 +0200 Subject: [PATCH 097/182] Move name parameter in field constructors to first position for consistency. --- src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs | 4 ++-- src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs index aa8b8eeb3..cc3bf9448 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs @@ -17,10 +17,10 @@ protected InstanceDataField(uint typeIndex) /// /// Creates a new instance data member. /// - /// The data type of the member. /// The name of the member. /// The byte offset within the class or structure that the member is stored at. - public InstanceDataField(CodeViewTypeRecord dataType, Utf8String name, ulong offset) + /// The data type of the member. + public InstanceDataField(Utf8String name, ulong offset, CodeViewTypeRecord dataType) : base(dataType, name) { Offset = offset; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs index 22ea56e56..b06398da4 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs @@ -17,9 +17,9 @@ protected StaticDataField(uint typeIndex) /// /// Creates a new static data member. /// - /// The data type of the member. /// The name. - public StaticDataField(CodeViewTypeRecord dataType, Utf8String name) + /// The data type of the member. + public StaticDataField(Utf8String name, CodeViewTypeRecord dataType) : base(dataType, name) { } From 4f30643907acafd59e82655ca2f5d78eb0cec530 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 11 Aug 2022 13:41:08 +0200 Subject: [PATCH 098/182] Rename type to leaf in PdbImage. --- src/AsmResolver.Symbols.Pdb/PdbImage.cs | 8 +++---- .../SerializedPdbImage.cs | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index cb0170dd5..0abd232a9 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -84,18 +84,18 @@ public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParamete /// Attempts to obtain a type record from the TPI or IPI stream based on its type index. /// /// The type index. - /// The resolved type. + /// The resolved type. /// true if the type was found, false otherwise. - public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? type) + public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf) { typeIndex &= 0x7fffffff; if (typeIndex is > 0 and < 0x1000) { - type = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleTypeRecord(i)); + leaf = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleTypeRecord(i)); return true; } - type = null; + leaf = null; return false; } diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index a12d26551..32bc7dcd6 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -61,7 +61,7 @@ internal TpiStream TpiStream } [MemberNotNull(nameof(_leaves))] - private void EnsureTypeArrayInitialized() + private void EnsureLeavesInitialized() { if (_leaves is null) { @@ -71,27 +71,27 @@ private void EnsureTypeArrayInitialized() } /// - public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? type) + public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf) { if (typeIndex < TpiStream.TypeIndexBegin) - return base.TryGetLeafRecord(typeIndex, out type); + return base.TryGetLeafRecord(typeIndex, out leaf); - EnsureTypeArrayInitialized(); + EnsureLeavesInitialized(); if (typeIndex >= TpiStream.TypeIndexBegin && typeIndex < TpiStream.TypeIndexEnd) { - type = _leaves[typeIndex - TpiStream.TypeIndexBegin]; - if (type is null && TpiStream.TryGetLeafRecordReader(typeIndex, out var reader)) + leaf = _leaves[typeIndex - TpiStream.TypeIndexBegin]; + if (leaf is null && TpiStream.TryGetLeafRecordReader(typeIndex, out var reader)) { - type = CodeViewLeaf.FromReader(ReaderContext, typeIndex, ref reader); - Interlocked.CompareExchange(ref _leaves[typeIndex - TpiStream.TypeIndexBegin], type, null); + leaf = CodeViewLeaf.FromReader(ReaderContext, typeIndex, ref reader); + Interlocked.CompareExchange(ref _leaves[typeIndex - TpiStream.TypeIndexBegin], leaf, null); } - type = _leaves[typeIndex - TpiStream.TypeIndexBegin]; - return type is not null; + leaf = _leaves[typeIndex - TpiStream.TypeIndexBegin]; + return leaf is not null; } - type = null; + leaf = null; return false; } From 90d2ba0def82ef4e524b4d060bf2948fe9d5c91d Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 16 Aug 2022 18:50:27 +0200 Subject: [PATCH 099/182] BUGFIX: Performance regression on creating new TypeDefOrRef and GenericInstance type signatures. --- .../Signatures/Types/GenericInstanceTypeSignature.cs | 2 +- .../Signatures/Types/TypeDefOrRefSignature.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 443854bd1..53a414283 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -57,7 +57,7 @@ public GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType) private GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType, IEnumerable typeArguments) { - GenericType = genericType; + _genericType = genericType; _typeArguments = new List(typeArguments); _isValueType = isValueType; } diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index fb989fa00..f617beb31 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -26,7 +26,7 @@ public TypeDefOrRefSignature(ITypeDefOrRef type) /// Indicates whether the referenced type is a value type or not. public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType) { - Type = type; + _type = type; _isValueType = isValueType; } From 505da7a9255a96bb649023f7a36b9d628a41ad63 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 19 Aug 2022 17:42:04 +0200 Subject: [PATCH 100/182] Add convenience properties in DotNetRuntimeInfo for quick classification of used runtime. --- src/AsmResolver.DotNet/DotNetRuntimeInfo.cs | 15 +++++++++++++++ .../ModuleDefinitionTest.cs | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs index 33813a6ed..d289f45f5 100644 --- a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs +++ b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs @@ -53,6 +53,21 @@ public Version Version get; } + /// + /// Gets a value indicating whether the application targets the .NET or .NET Core runtime or not. + /// + public bool IsNetCoreApp => Name == NetCoreApp; + + /// + /// Gets a value indicating whether the application targets the .NET Framework runtime or not. + /// + public bool IsNetFramework => Name == NetFramework; + + /// + /// Gets a value indicating whether the application targets the .NET standard specification or not. + /// + public bool IsNetStandard => Name == NetStandard; + /// /// Attempts to parse the framework name as provided in . /// diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index d085b63cc..62b350668 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -327,6 +327,7 @@ public void PersistentFileReferences() public void DetectTargetNetFramework40() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.True(module.OriginalTargetRuntime.IsNetFramework); Assert.Contains(DotNetRuntimeInfo.NetFramework, module.OriginalTargetRuntime.Name); Assert.Equal(4, module.OriginalTargetRuntime.Version.Major); Assert.Equal(0, module.OriginalTargetRuntime.Version.Minor); @@ -336,6 +337,7 @@ public void DetectTargetNetFramework40() public void DetectTargetNetCore() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); + Assert.True(module.OriginalTargetRuntime.IsNetCoreApp); Assert.Contains(DotNetRuntimeInfo.NetCoreApp, module.OriginalTargetRuntime.Name); Assert.Equal(2, module.OriginalTargetRuntime.Version.Major); Assert.Equal(2, module.OriginalTargetRuntime.Version.Minor); @@ -345,6 +347,7 @@ public void DetectTargetNetCore() public void DetectTargetStandard() { var module = ModuleDefinition.FromFile(typeof(TestCases.Types.Class).Assembly.Location); + Assert.True(module.OriginalTargetRuntime.IsNetStandard); Assert.Contains(DotNetRuntimeInfo.NetStandard, module.OriginalTargetRuntime.Name); Assert.Equal(2, module.OriginalTargetRuntime.Version.Major); } From bf3b6cbfe171d81ca3011a66911d7eabbc9eec3b Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 19 Aug 2022 18:13:15 +0200 Subject: [PATCH 101/182] BUGFIX: Check if .net directory exists before using it while preserving tokens. --- .../Builder/Discovery/MemberDiscoverer.cs | 7 +++- .../Builder/DotNetDirectoryFactory.cs | 3 ++ .../Builder/ManagedPEImageBuilderTest.cs | 37 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs index d6eeac3c6..30750b64a 100644 --- a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs +++ b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs @@ -121,6 +121,9 @@ public static MemberDiscoveryResult DiscoverMembersInModule(ModuleDefinition mod private void CollectExistingMembers() { + if (_module.DotNetDirectory?.Metadata is null) + return; + if ((_flags & MemberDiscoveryFlags.PreserveTypeOrder) != 0) CollectMembersFromTable(TableIndex.TypeDef); if ((_flags & MemberDiscoveryFlags.PreserveFieldOrder) != 0) @@ -139,8 +142,8 @@ private void CollectMembersFromTable(TableIndex tableIndex) where TMember: IMetadataMember, IModuleProvider { // Get original number of elements in the table. - int count = _module.DotNetDirectory!.Metadata - !.GetStream() + int count = _module.DotNetDirectory!.Metadata! + .GetStream() .GetTable(tableIndex) .Count; diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs index 7c0619f9f..7750a6146 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs @@ -230,6 +230,9 @@ private void ImportBasicTablesIfSpecified(ModuleDefinition module, DotNetDirecto private void ImportTypeSpecsAndMemberRefsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) { + if (module.DotNetDirectory is null) + return; + if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeSpecificationIndices) != 0) { ImportTables(module, TableIndex.TypeSpec, diff --git a/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs b/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs index 583238a58..44daafcce 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using AsmResolver.DotNet.Builder; using AsmResolver.PE; using Xunit; @@ -42,5 +43,41 @@ public void ExecutableImportDirectoryShouldContainMsCoreeCorDllMain() relocation.Location.CanRead && relocation.Location.CreateReader().ReadUInt32() == image.ImageBase + symbol.AddressTableEntry!.Rva); } + + [Fact] + public void ConstructPEImageFromNewModuleWithNoPreservation() + { + var module = new ModuleDefinition("Module"); + var result = module.ToPEImage(); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromNewModuleWithPreservation() + { + var module = new ModuleDefinition("Module"); + var result = module.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromExistingModuleWithNoPreservation() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var result = module.ToPEImage(); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromExistingModuleWithPreservation() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var result = module.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } } } From cb283fc880f0308d82589c2482fdf0533d4fdbd4 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 20:08:17 +0200 Subject: [PATCH 102/182] Refactor ISegment.UpdateOffsets to use RelocationParameters instead. --- .../Bundles/BundleManifest.cs | 2 +- src/AsmResolver.PE.File/PEFile.cs | 32 +++-- src/AsmResolver.PE.File/PESection.cs | 2 +- src/AsmResolver.PE.File/PESegmentReference.cs | 6 - src/AsmResolver.PE.File/SerializedPEFile.cs | 11 +- .../Version/FixedVersionInfo.cs | 2 +- .../Debug/Builder/DebugDirectoryBuffer.cs | 2 +- .../Debug/CustomDebugDataSegment.cs | 4 +- .../DotNet/Builder/DotNetSegmentBuffer.cs | 2 +- .../DotNet/Builder/MethodBodyTableBuffer.cs | 2 +- .../DotNet/Cil/CilRawFatMethodBody.cs | 18 +-- .../DotNet/Cil/CilRawTinyMethodBody.cs | 8 +- .../DotNet/Metadata/CustomMetadataStream.cs | 2 +- .../DotNet/SerializedDotNetDirectory.cs | 2 +- .../DotNet/VTableFixups/VTableFixup.cs | 4 +- .../VTableFixups/VTableFixupsDirectory.cs | 6 +- .../VTableFixups/VTableTokenCollection.cs | 6 +- .../Exceptions/X64/X64UnwindInfo.cs | 3 +- .../Exports/Builder/ExportDirectoryBuffer.cs | 6 +- .../Imports/Builder/HintNameTableBuffer.cs | 12 +- .../Builder/ImportAddressDirectoryBuffer.cs | 14 ++- .../Imports/Builder/ImportDirectoryBuffer.cs | 15 ++- src/AsmResolver.PE/PEReaderContext.cs | 17 ++- .../Builder/RelocationsDirectoryBuffer.cs | 4 +- .../Tls/TlsCallbackCollection.cs | 6 +- .../Builder/ResourceDirectoryBuffer.cs | 2 +- src/AsmResolver/DataSourceSegment.cs | 6 +- src/AsmResolver/IOffsetProvider.cs | 16 +-- src/AsmResolver/ISegment.cs | 14 +++ src/AsmResolver/RelativeReference.cs | 7 -- src/AsmResolver/RelocationParameters.cs | 118 ++++++++++++++++++ src/AsmResolver/SegmentBase.cs | 6 +- src/AsmResolver/SegmentBuilder.cs | 26 ++-- src/AsmResolver/SegmentReference.cs | 3 - src/AsmResolver/VirtualSegment.cs | 16 +-- test/AsmResolver.PE.Tests/VirtualAddress.cs | 4 - test/AsmResolver.Tests/SegmentBuilderTest.cs | 12 +- 37 files changed, 267 insertions(+), 151 deletions(-) create mode 100644 src/AsmResolver/RelocationParameters.cs diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index 2acc2a57b..c3a6025cf 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -445,7 +445,7 @@ private void WriteFileContents(IBinaryStreamWriter writer, uint alignment) if (file.Type == BundleFileType.Assembly) writer.Align(alignment); - file.Contents.UpdateOffsets(writer.Offset, (uint) writer.Offset); + file.Contents.UpdateOffsets(new RelocationParameters(writer.Offset, (uint) writer.Offset)); file.Contents.Write(writer); } } diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index d1dc0f2d7..73cc0b9cf 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -371,12 +371,15 @@ public void UpdateHeaders() FileHeader.NumberOfSections = (ushort) Sections.Count; - FileHeader.UpdateOffsets( + var relocation = new RelocationParameters(OptionalHeader.ImageBase, 0, 0, + OptionalHeader.Magic == OptionalHeaderMagic.Pe32); + + FileHeader.UpdateOffsets(relocation.WithOffsetRva( DosHeader.NextHeaderOffset + 4, - DosHeader.NextHeaderOffset + 4); - OptionalHeader.UpdateOffsets( + DosHeader.NextHeaderOffset + 4)); + OptionalHeader.UpdateOffsets(relocation.WithOffsetRva( FileHeader.Offset + FileHeader.GetPhysicalSize(), - FileHeader.Rva + FileHeader.GetVirtualSize()); + FileHeader.Rva + FileHeader.GetVirtualSize())); FileHeader.SizeOfOptionalHeader = (ushort) OptionalHeader.GetPhysicalSize(); OptionalHeader.SizeOfHeaders = (uint) (OptionalHeader.Offset @@ -391,7 +394,9 @@ public void UpdateHeaders() OptionalHeader.SizeOfImage = lastSection.Rva + lastSection.GetVirtualSize().Align(OptionalHeader.SectionAlignment); - EofData?.UpdateOffsets(lastSection.Offset + lastSection.GetPhysicalSize(), OptionalHeader.SizeOfImage); + EofData?.UpdateOffsets(relocation.WithOffsetRva( + lastSection.Offset + lastSection.GetPhysicalSize(), + OptionalHeader.SizeOfImage)); } /// @@ -399,19 +404,20 @@ public void UpdateHeaders() /// public void AlignSections() { - uint currentFileOffset = OptionalHeader.SizeOfHeaders; + var relocation = new RelocationParameters( + OptionalHeader.ImageBase, + OptionalHeader.SizeOfHeaders.Align(OptionalHeader.FileAlignment), + OptionalHeader.SizeOfHeaders.Align(OptionalHeader.SectionAlignment), + OptionalHeader.Magic == OptionalHeaderMagic.Pe32); for (int i = 0; i < Sections.Count; i++) { var section = Sections[i]; - uint rva = i > 0 - ? Sections[i - 1].Rva + Sections[i - 1].GetVirtualSize() - : OptionalHeader.SizeOfHeaders.Align(OptionalHeader.SectionAlignment); - - currentFileOffset = currentFileOffset.Align(OptionalHeader.FileAlignment); - section.UpdateOffsets(currentFileOffset, rva.Align(OptionalHeader.SectionAlignment)); - currentFileOffset += section.GetPhysicalSize(); + section.UpdateOffsets(relocation); + relocation = relocation.Advance( + section.GetPhysicalSize().Align(OptionalHeader.FileAlignment), + section.GetVirtualSize().Align(OptionalHeader.SectionAlignment)); } } diff --git a/src/AsmResolver.PE.File/PESection.cs b/src/AsmResolver.PE.File/PESection.cs index 8338dd51f..b091780ae 100644 --- a/src/AsmResolver.PE.File/PESection.cs +++ b/src/AsmResolver.PE.File/PESection.cs @@ -216,7 +216,7 @@ public bool IsMemoryWrite public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) => Contents?.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => Contents?.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => Contents?.GetPhysicalSize() ?? 0; diff --git a/src/AsmResolver.PE.File/PESegmentReference.cs b/src/AsmResolver.PE.File/PESegmentReference.cs index 5c7326cbc..9eeeaea0a 100644 --- a/src/AsmResolver.PE.File/PESegmentReference.cs +++ b/src/AsmResolver.PE.File/PESegmentReference.cs @@ -32,18 +32,12 @@ public uint Rva get; } - /// - bool IOffsetProvider.CanUpdateOffsets => false; - /// public bool CanRead => _peFile.TryGetSectionContainingRva(Rva, out _); /// public bool IsBounded => false; - /// - void IOffsetProvider.UpdateOffsets(ulong newOffset, uint newRva) => throw new InvalidOperationException(); - /// public BinaryStreamReader CreateReader() => _peFile.CreateReaderAtRva(Rva); diff --git a/src/AsmResolver.PE.File/SerializedPEFile.cs b/src/AsmResolver.PE.File/SerializedPEFile.cs index c9bff4dab..a29c30d1b 100644 --- a/src/AsmResolver.PE.File/SerializedPEFile.cs +++ b/src/AsmResolver.PE.File/SerializedPEFile.cs @@ -12,6 +12,8 @@ public class SerializedPEFile : PEFile { private readonly List _sectionHeaders; private readonly BinaryStreamReader _reader; + private readonly ulong _originalImageBase; + private readonly bool _is32Bit; /// /// Reads a PE file from an input stream. @@ -36,6 +38,8 @@ public SerializedPEFile(in BinaryStreamReader reader, PEMappingMode mode) // Read NT headers. FileHeader = FileHeader.FromReader(ref _reader); OptionalHeader = OptionalHeader.FromReader(ref _reader); + _originalImageBase = OptionalHeader.ImageBase; + _is32Bit = OptionalHeader.Magic == OptionalHeaderMagic.Pe32; // Read section headers. _reader.Offset = OptionalHeader.Offset + FileHeader.SizeOfOptionalHeader; @@ -70,7 +74,12 @@ protected override IList GetSections() physicalContents = new DataSourceSegment(_reader.DataSource, offset, header.VirtualAddress, size); var virtualSegment = new VirtualSegment(physicalContents, header.VirtualSize); - virtualSegment.UpdateOffsets(offset, header.VirtualAddress); + virtualSegment.UpdateOffsets(new RelocationParameters( + _originalImageBase, + offset, + header.VirtualAddress, + _is32Bit)); + result.Add(new PESection(header, virtualSegment)); } diff --git a/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs b/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs index 03e8f4979..d698bca04 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs @@ -24,7 +24,7 @@ public class FixedVersionInfo : SegmentBase public static FixedVersionInfo FromReader(ref BinaryStreamReader reader) { var result = new FixedVersionInfo(); - result.UpdateOffsets(reader.Offset, reader.Rva); + result.UpdateOffsets(new RelocationParameters(reader.Offset, reader.Rva)); uint signature = reader.ReadUInt32(); if (signature != Signature) diff --git a/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs b/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs index bf5ce6ac0..e5afa4e88 100644 --- a/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs @@ -41,7 +41,7 @@ public void AddEntry(DebugDataEntry entry) public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _headers.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _headers.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _headers.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs b/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs index ec05a4438..98af4faf0 100644 --- a/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs +++ b/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs @@ -45,10 +45,10 @@ public DebugDataType Type public bool CanUpdateOffsets => Contents?.CanUpdateOffsets ?? false; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { if (Contents != null) - Contents.UpdateOffsets(newOffset, newRva); + Contents.UpdateOffsets(parameters); else throw new ArgumentNullException(nameof(Contents)); } diff --git a/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs b/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs index 44cfd21ef..fe345de97 100644 --- a/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs +++ b/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs @@ -73,7 +73,7 @@ private void AddIfPresent(ISegment? segment) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _segments.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs b/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs index 71d971514..76b0a93f5 100644 --- a/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs +++ b/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs @@ -53,7 +53,7 @@ public void AddCilBody(CilRawMethodBody body) public void AddNativeBody(ISegment body, uint alignment) => _nativeBodies.Add(body, alignment); /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters); /// public uint GetPhysicalSize() diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs index 69a4a9582..a803455c0 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs @@ -153,7 +153,7 @@ public IList ExtraSections // Create body. var body = new CilRawFatMethodBody(flags, maxStack, localVarSigToken, code); - body.UpdateOffsets(fileOffset, rva); + body.UpdateOffsets(new RelocationParameters(fileOffset, rva)); // Read any extra sections. if (body.HasSections) @@ -172,18 +172,22 @@ public IList ExtraSections } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - Code.UpdateOffsets(newOffset + 12, newRva + 12); + base.UpdateOffsets(parameters); + + var current = parameters.Advance(12); + Code.UpdateOffsets(current); if (HasSections) { uint codeSize = Code.GetPhysicalSize(); - newOffset = (Code.Offset + codeSize).Align(4); - newRva = (Code.Rva + codeSize).Align(4); + current = current.Advance(codeSize).Align(4); for (int i = 0; i < ExtraSections.Count; i++) - ExtraSections[i].UpdateOffsets(newOffset, newRva); + { + ExtraSections[i].UpdateOffsets(current); + current = current.Advance(ExtraSections[i].GetPhysicalSize()); + } } } diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs index 5c35165ad..e4532f00c 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs @@ -58,15 +58,15 @@ public CilRawTinyMethodBody(IReadableSegment code) uint codeSize = (uint) flag >> 2; var methodBody = new CilRawTinyMethodBody(reader.ReadSegment(codeSize)); - methodBody.UpdateOffsets(fileOffset, rva); + methodBody.UpdateOffsets(new RelocationParameters(fileOffset, rva)); return methodBody; } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - Code.UpdateOffsets(newOffset + 1, newRva + 1); + base.UpdateOffsets(parameters); + Code.UpdateOffsets(parameters.Advance(1)); } /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs b/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs index b39a0ba3f..056f2b274 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs @@ -68,7 +68,7 @@ public BinaryStreamReader CreateReader() } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => Contents.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => Contents.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => Contents.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs index c75dd2a25..bf5fdb1b2 100644 --- a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs @@ -126,7 +126,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader } var vtables = new VTableFixupsDirectory(); - vtables.UpdateOffsets(directoryReader.Offset, directoryReader.Rva); + vtables.UpdateOffsets(_context.GetRelocation(directoryReader.Offset, directoryReader.Rva)); for (int i = 0; i < directoryReader.Length / 8; i++) { diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs index 97d19c050..4b2ebcbe4 100644 --- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs +++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs @@ -41,8 +41,8 @@ public VTableTokenCollection Tokens ushort entries = reader.ReadUInt16(); var vtable = new VTableFixup((VTableType) reader.ReadUInt16()); - vtable.UpdateOffsets(offset, rva); - vtable.Tokens.UpdateOffsets(tableReader.Offset, tableReader.Rva); + vtable.UpdateOffsets(context.GetRelocation(offset, rva)); + vtable.Tokens.UpdateOffsets(context.GetRelocation(tableReader.Offset, tableReader.Rva)); for (int i = 0; i < entries; i++) { diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs index d5c9e9ae2..acf7c4db5 100644 --- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs +++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs @@ -27,10 +27,10 @@ public uint Rva public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; } /// diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs index 27f8998ac..4420f1fc2 100644 --- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs +++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs @@ -45,10 +45,10 @@ protected override void InsertItem(int index, MetadataToken item) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; } /// diff --git a/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs b/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs index 4074e0bdf..d00d712bf 100644 --- a/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs +++ b/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs @@ -1,5 +1,6 @@ using System; using AsmResolver.IO; +using AsmResolver.PE.File.Headers; namespace AsmResolver.PE.Exceptions.X64 { @@ -151,7 +152,7 @@ public ISegmentReference ExceptionHandlerData public static X64UnwindInfo FromReader(PEReaderContext context, ref BinaryStreamReader reader) { var result = new X64UnwindInfo(); - result.UpdateOffsets(reader.Offset, reader.Rva); + result.UpdateOffsets(context.GetRelocation(reader.Offset, reader.Rva)); result._firstByte = reader.ReadByte(); result.SizeOfProlog = reader.ReadByte(); diff --git a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs index 8bf5526fd..fc7e4ebac 100644 --- a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs @@ -83,10 +83,10 @@ public void AddDirectory(IExportDirectory exportDirectory) } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - _contentsBuilder.UpdateOffsets(newOffset + ExportDirectoryHeaderSize, newRva + ExportDirectoryHeaderSize); + base.UpdateOffsets(parameters); + _contentsBuilder.UpdateOffsets(parameters.Advance(ExportDirectoryHeaderSize)); } /// diff --git a/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs b/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs index f2a94395f..62275e716 100644 --- a/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs @@ -15,23 +15,23 @@ public class HintNameTableBuffer : SegmentBase private uint _length; /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - ulong currentOffset = newOffset; + ulong currentOffset = parameters.Offset; foreach (var module in _modules) { foreach (var entry in module.Symbols) { if (entry.IsImportByName) { - _hintNameOffsets[entry] = (uint) (currentOffset - newOffset); + _hintNameOffsets[entry] = (uint) (currentOffset - parameters.Offset); currentOffset += (uint) (sizeof(ushort) + Encoding.ASCII.GetByteCount(entry.Name) + 1); currentOffset = currentOffset.Align(2); } } - _moduleNameOffsets[module] = (uint) (currentOffset - newOffset); + _moduleNameOffsets[module] = (uint) (currentOffset - parameters.Offset); if (module.Name is not null) currentOffset += (uint) Encoding.ASCII.GetByteCount(module.Name); @@ -39,7 +39,7 @@ public override void UpdateOffsets(ulong newOffset, uint newRva) currentOffset++; } - _length = (uint) (currentOffset - newOffset); + _length = (uint) (currentOffset - parameters.Offset); } /// diff --git a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs index 7bcc57074..6565ba4a1 100644 --- a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs @@ -17,17 +17,19 @@ public ImportAddressDirectoryBuffer(HintNameTableBuffer hintNameTable, bool is32 } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - foreach (var module in Modules) + var current = parameters; + for (int i = 0; i < Modules.Count; i++) { + var module = Modules[i]; + var thunkTable = GetModuleThunkTable(module); uint size = thunkTable.GetPhysicalSize(); - thunkTable.UpdateOffsets(newOffset, newRva); - newOffset += size; - newRva += size; + thunkTable.UpdateOffsets(current); + current = current.Advance(size); } } diff --git a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs index bda2b1f17..cd61d5447 100644 --- a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs @@ -42,23 +42,22 @@ public override void AddModule(IImportedModule module) } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - newOffset += _entriesLength; - newRva += _entriesLength; + var current = parameters.Advance(_entriesLength); foreach (var module in Modules) { var thunkTable = GetModuleThunkTable(module); uint size = thunkTable.GetPhysicalSize(); - thunkTable.UpdateOffsets(newOffset, newRva); - newOffset += size; - newRva += size; + thunkTable.UpdateOffsets(current); + + current = current.Advance(size); } - HintNameTable.UpdateOffsets(newOffset, newRva); + HintNameTable.UpdateOffsets(current); } /// diff --git a/src/AsmResolver.PE/PEReaderContext.cs b/src/AsmResolver.PE/PEReaderContext.cs index 300247911..f5f696aaa 100644 --- a/src/AsmResolver.PE/PEReaderContext.cs +++ b/src/AsmResolver.PE/PEReaderContext.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; namespace AsmResolver.PE { @@ -29,7 +30,7 @@ public PEReaderContext(IPEFile file, PEReaderParameters parameters) File = file; Parameters = parameters; } - + /// /// Gets the original PE file that is being parsed. /// @@ -46,10 +47,22 @@ public PEReaderParameters Parameters get; } + /// + /// Creates relocation parameters based on the current PE file that is being read. + /// + /// The offset of the segment. + /// The relative virtual address of the segment. + /// The created relocation parameters. + public RelocationParameters GetRelocation(ulong offset, uint rva) + { + return new RelocationParameters(File.OptionalHeader.ImageBase, offset, rva, + File.OptionalHeader.Magic == OptionalHeaderMagic.Pe32); + } + /// public void MarkAsFatal() => Parameters.ErrorListener.MarkAsFatal(); /// public void RegisterException(Exception exception) => Parameters.ErrorListener.RegisterException(exception); } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs b/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs index 71d31c474..92aa70a2e 100644 --- a/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs @@ -58,9 +58,9 @@ private static RelocationBlock GetOrCreateBlock(IDictionary - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); _blocks = null; } diff --git a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs index ddeb3f368..102edeaf8 100644 --- a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs +++ b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs @@ -33,10 +33,10 @@ public uint Rva public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; } /// diff --git a/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs b/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs index 987dc8412..ea7bc9067 100644 --- a/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs @@ -104,7 +104,7 @@ private void AddDataEntry(IResourceEntry entry) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _segments.GetPhysicalSize(); diff --git a/src/AsmResolver/DataSourceSegment.cs b/src/AsmResolver/DataSourceSegment.cs index a8df578f7..8b237906e 100644 --- a/src/AsmResolver/DataSourceSegment.cs +++ b/src/AsmResolver/DataSourceSegment.cs @@ -25,11 +25,11 @@ public DataSourceSegment(IDataSource dataSource, ulong offset, uint rva, uint si } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - long displacement = (long) newOffset - (long) _originalOffset; + long displacement = (long) parameters.Offset - (long) _originalOffset; _displacedDataSource = displacement != 0 ? new DisplacedDataSource(_dataSource, displacement) : null; diff --git a/src/AsmResolver/IOffsetProvider.cs b/src/AsmResolver/IOffsetProvider.cs index 185b9c85e..5f83e16e9 100644 --- a/src/AsmResolver/IOffsetProvider.cs +++ b/src/AsmResolver/IOffsetProvider.cs @@ -20,20 +20,6 @@ uint Rva { get; } - - /// - /// Determines whether this structure can be relocated to another offset or virtual address. - /// - bool CanUpdateOffsets - { - get; - } - - /// - /// Assigns a new file and virtual offset to the segment and all its sub-components. - /// - /// The new file offset. - /// The new virtual offset. - void UpdateOffsets(ulong newOffset, uint newRva); } + } diff --git a/src/AsmResolver/ISegment.cs b/src/AsmResolver/ISegment.cs index f24ea98c1..dce4aec3c 100644 --- a/src/AsmResolver/ISegment.cs +++ b/src/AsmResolver/ISegment.cs @@ -9,12 +9,26 @@ namespace AsmResolver /// public interface ISegment : IOffsetProvider, IWritable { + /// + /// Determines whether this structure can be relocated to another offset or virtual address. + /// + bool CanUpdateOffsets + { + get; + } + /// /// Computes the number of bytes the segment will contain when it is mapped into memory. /// /// The number of bytes. uint GetVirtualSize(); + /// + /// Assigns a new file and virtual offset to the segment and all its sub-components. + /// + /// The parameters containing the new offset information for the segment. + void UpdateOffsets(in RelocationParameters parameters); + } public static partial class Extensions diff --git a/src/AsmResolver/RelativeReference.cs b/src/AsmResolver/RelativeReference.cs index cc31132fd..24fa44250 100644 --- a/src/AsmResolver/RelativeReference.cs +++ b/src/AsmResolver/RelativeReference.cs @@ -41,13 +41,6 @@ public int Additive /// public uint Rva => (uint) (Base.Rva + Additive); - /// - public bool CanUpdateOffsets => Base.CanUpdateOffsets; - - /// - public void UpdateOffsets(ulong newOffset, uint newRva) => - Base.UpdateOffsets( newOffset - (ulong) Additive, (uint) (newRva - Additive)); - /// public bool CanRead => Base is ISegmentReference reference && reference.CanRead; diff --git a/src/AsmResolver/RelocationParameters.cs b/src/AsmResolver/RelocationParameters.cs new file mode 100644 index 000000000..c6d3f546e --- /dev/null +++ b/src/AsmResolver/RelocationParameters.cs @@ -0,0 +1,118 @@ +namespace AsmResolver +{ + /// + /// Provides parameters for relocating a segment to a new offset-rva pair. + /// + public readonly struct RelocationParameters + { + /// + /// Creates new relocation parameters. + /// + /// The new offset of the segment. + /// The new virtual address of the segment, relative to the image base. + public RelocationParameters(ulong offset, uint rva) + : this(0, offset, rva, true) + { + } + + /// + /// Creates new relocation parameters. + /// + /// The base address of the image the segment is located in. + /// The new offset of the segment. + /// The new virtual address of the segment, relative to the image base. + /// true if the image is targeting 32-bit images, false for 64-bit images. + public RelocationParameters(ulong imageBase, ulong offset, uint rva, bool is32Bit) + { + ImageBase = imageBase; + Offset = offset; + Rva = rva; + Is32Bit = is32Bit; + } + + /// + /// Gets the image base that is assumed when relocating the segment. + /// + public ulong ImageBase + { + get; + } + + /// + /// Gets the new physical offset of the segment. + /// + public ulong Offset + { + get; + } + + /// + /// Gets the new virtual address of the segment, relative to the image base. + /// + public uint Rva + { + get; + } + + /// + /// Gets a value indicating whether the image is targeting 32-bit machines. + /// + public bool Is32Bit + { + get; + } + + /// + /// Gets a value indicating whether the image is targeting 64-bit machines. + /// + public bool Is64Bit => !Is32Bit; + + /// + /// Copies the current relocation parameters, and assigns a new offset and relative virtual address. + /// + /// The new offset. + /// The new relative virtual address. + /// The new relocation parameters. + public RelocationParameters WithOffsetRva(ulong offset, uint rva) + { + return new RelocationParameters(ImageBase, offset, rva, Is32Bit); + } + + /// + /// Aligns the current offset and virtual address to the nearest multiple of the provided alignment. + /// + /// The alignment. + /// The new relocation parameters. + public RelocationParameters Align(uint alignment) + { + return WithOffsetRva(Offset.Align(alignment), Rva.Align(alignment)); + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance with. + /// The new relocation parameters. + public RelocationParameters Advance(uint count) + { + return WithOffsetRva(Offset + count, Rva + count); + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance the physical offset with. + /// The number of bytes to advance the virtual address with. + /// The new relocation parameters. + public RelocationParameters Advance(uint physicalCount, uint virtualCount) + { + return WithOffsetRva(Offset + physicalCount, Rva + virtualCount); + } + + /// + public override string ToString() + { + return $"{nameof(ImageBase)}: {ImageBase:X8}, {nameof(Offset)}: {Offset:X8}, {nameof(Rva)}: {Rva:X8}"; + } + } +} diff --git a/src/AsmResolver/SegmentBase.cs b/src/AsmResolver/SegmentBase.cs index 6013990ef..264e1925a 100644 --- a/src/AsmResolver/SegmentBase.cs +++ b/src/AsmResolver/SegmentBase.cs @@ -25,10 +25,10 @@ public uint Rva public bool CanUpdateOffsets => true; /// - public virtual void UpdateOffsets(ulong newOffset, uint newRva) + public virtual void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; } /// diff --git a/src/AsmResolver/SegmentBuilder.cs b/src/AsmResolver/SegmentBuilder.cs index 734d917d5..09fcb0fed 100644 --- a/src/AsmResolver/SegmentBuilder.cs +++ b/src/AsmResolver/SegmentBuilder.cs @@ -55,31 +55,25 @@ public void Add(ISegment segment, uint alignment) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; - _physicalSize = 0; - _virtualSize = 0; + Offset = parameters.Offset; + Rva = parameters.Rva; + var current = parameters; foreach (var item in _items) { - uint physicalPadding = (uint) (newOffset.Align(item.Alignment) - newOffset); - uint virtualPadding = newRva.Align(item.Alignment) - newRva; - - newOffset += physicalPadding; - newRva += virtualPadding; - - item.Segment.UpdateOffsets(newOffset, newRva); + current = current.Align(item.Alignment); + item.Segment.UpdateOffsets(current); uint physicalSize = item.Segment.GetPhysicalSize(); uint virtualSize = item.Segment.GetVirtualSize(); - newOffset += physicalSize; - newRva += virtualSize; - _physicalSize += physicalPadding + physicalSize; - _virtualSize += virtualPadding + virtualSize; + current = current.Advance(physicalSize, virtualSize); } + + _physicalSize = (uint) (current.Offset - parameters.Offset); + _virtualSize = (uint) (current.Rva - parameters.Rva); } /// diff --git a/src/AsmResolver/SegmentReference.cs b/src/AsmResolver/SegmentReference.cs index cb9aa1e0d..b14135904 100644 --- a/src/AsmResolver/SegmentReference.cs +++ b/src/AsmResolver/SegmentReference.cs @@ -50,9 +50,6 @@ public SegmentReference(ISegment? segment) get; } - /// - public void UpdateOffsets(ulong newOffset, uint newRva) => Segment?.UpdateOffsets(newOffset, newRva); - /// public BinaryStreamReader CreateReader() { diff --git a/src/AsmResolver/VirtualSegment.cs b/src/AsmResolver/VirtualSegment.cs index 5b31c2e39..dd2b08022 100644 --- a/src/AsmResolver/VirtualSegment.cs +++ b/src/AsmResolver/VirtualSegment.cs @@ -44,15 +44,7 @@ public uint VirtualSize public ulong Offset => PhysicalContents?.Offset ?? 0; /// - public uint Rva - { - get => _rva; - set - { - _rva = value; - PhysicalContents?.UpdateOffsets(Offset, value); - } - } + public uint Rva => _rva; /// public bool CanUpdateOffsets => PhysicalContents?.CanUpdateOffsets ?? false; @@ -64,10 +56,10 @@ public uint Rva public bool IsReadable => PhysicalContents is IReadableSegment; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - _rva = newRva; - PhysicalContents?.UpdateOffsets(newOffset, newRva); + _rva = parameters.Rva; + PhysicalContents?.UpdateOffsets(parameters); } /// diff --git a/test/AsmResolver.PE.Tests/VirtualAddress.cs b/test/AsmResolver.PE.Tests/VirtualAddress.cs index b9424e496..1b48f3761 100644 --- a/test/AsmResolver.PE.Tests/VirtualAddress.cs +++ b/test/AsmResolver.PE.Tests/VirtualAddress.cs @@ -17,10 +17,6 @@ public uint Rva get; } - bool IOffsetProvider.CanUpdateOffsets => false; - - void IOffsetProvider.UpdateOffsets(ulong newOffset, uint newRva) => throw new InvalidOperationException(); - public bool CanRead => false; bool ISegmentReference.IsBounded => false; diff --git a/test/AsmResolver.Tests/SegmentBuilderTest.cs b/test/AsmResolver.Tests/SegmentBuilderTest.cs index d64aa1c78..be2aff523 100644 --- a/test/AsmResolver.Tests/SegmentBuilderTest.cs +++ b/test/AsmResolver.Tests/SegmentBuilderTest.cs @@ -8,8 +8,6 @@ public class SegmentBuilderTest { private static byte[] ToBytes(ISegment segment) { - segment.UpdateOffsets(0, 0); - using var stream = new MemoryStream(); var writer = new BinaryStreamWriter(stream); @@ -22,7 +20,7 @@ public void EmptyNoAlignment() { var collection = new SegmentBuilder(); - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, collection.Offset); Assert.Equal(0x1000u, collection.Rva); @@ -39,7 +37,7 @@ public void SingleItemNoAlignment() var collection = new SegmentBuilder {segment}; - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment.Offset); Assert.Equal(0x1000u, segment.Rva); @@ -61,7 +59,7 @@ public void MultipleItemsNoAlignment() var collection = new SegmentBuilder {segment1, segment2, segment3}; - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment1.Offset); Assert.Equal(0x1000u, segment1.Rva); @@ -88,7 +86,7 @@ public void SingleItemAlignment() var builder = new SegmentBuilder {segment}; - builder.UpdateOffsets(0x400, 0x1000); + builder.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment.Offset); @@ -115,7 +113,7 @@ public void MultipleItemsAlignment() {segment3, 8} }; - builder.UpdateOffsets(0x400, 0x1000); + builder.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment1.Offset); Assert.Equal(0x1000u, segment1.Rva); From 27d4def912b6308c570bbadf65029fbfa93e576d Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 20:14:37 +0200 Subject: [PATCH 103/182] Remove public imagebase and 32bit properties in TLS directory, and use new UpdateOffsets instead. --- src/AsmResolver.PE/Tls/ITlsDirectory.cs | 18 ----------- .../Tls/SerializedTlsDirectory.cs | 14 ++++----- .../Tls/TlsCallbackCollection.cs | 10 +++++-- src/AsmResolver.PE/Tls/TlsDirectory.cs | 30 ++++++++----------- .../Tls/TlsDirectoryTest.cs | 2 -- 5 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/AsmResolver.PE/Tls/ITlsDirectory.cs b/src/AsmResolver.PE/Tls/ITlsDirectory.cs index b07ec8000..9149c0314 100644 --- a/src/AsmResolver.PE/Tls/ITlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/ITlsDirectory.cs @@ -54,24 +54,6 @@ TlsCharacteristics Characteristics set; } - /// - /// Gets or sets the image base address that the TLS directory assumes. - /// - public ulong ImageBase - { - get; - set; - } - - /// - /// Gets or sets a value indicating whether the TLS directory assumes a 32-bit or 64-bit format. - /// - public bool Is32Bit - { - get; - set; - } - /// /// Obtains a collection of base address relocations that need to be applied to the TLS data directory /// after the image was loaded into memory. diff --git a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs index 99c32faa1..afece2777 100644 --- a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs @@ -22,18 +22,16 @@ public SerializedTlsDirectory(PEReaderContext context, ref BinaryStreamReader re { _context = context; - ulong imageBase = context.File.OptionalHeader.ImageBase; - bool is32Bit = context.File.OptionalHeader.Magic == OptionalHeaderMagic.Pe32; + var relocation = context.GetRelocation(reader.Offset, reader.Rva); - _templateStart = reader.ReadNativeInt(is32Bit); - _templateEnd = reader.ReadNativeInt(is32Bit); - Index = context.File.GetReferenceToRva((uint)(reader.ReadNativeInt(is32Bit) - imageBase)); - _addressOfCallbacks = reader.ReadNativeInt(is32Bit); + _templateStart = reader.ReadNativeInt(relocation.Is32Bit); + _templateEnd = reader.ReadNativeInt(relocation.Is32Bit); + Index = context.File.GetReferenceToRva((uint)(reader.ReadNativeInt(relocation.Is32Bit) - relocation.ImageBase)); + _addressOfCallbacks = reader.ReadNativeInt(relocation.Is32Bit); SizeOfZeroFill = reader.ReadUInt32(); Characteristics = (TlsCharacteristics) reader.ReadUInt32(); - ImageBase = imageBase; - Is32Bit = is32Bit; + UpdateOffsets(relocation); } /// diff --git a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs index 102edeaf8..dc29af2aa 100644 --- a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs +++ b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs @@ -9,6 +9,8 @@ namespace AsmResolver.PE.Tls public class TlsCallbackCollection : Collection, ISegment { private readonly ITlsDirectory _owner; + private ulong _imageBase = 0x00400000; + private bool _is32Bit = true; internal TlsCallbackCollection(ITlsDirectory owner) { @@ -37,12 +39,14 @@ public void UpdateOffsets(in RelocationParameters parameters) { Offset = parameters.Offset; Rva = parameters.Rva; + _imageBase = parameters.ImageBase; + _is32Bit = parameters.Is32Bit; } /// public uint GetPhysicalSize() { - uint pointerSize = (uint) (_owner.Is32Bit ? sizeof(uint) : sizeof(ulong)); + uint pointerSize = (uint) (_is32Bit ? sizeof(uint) : sizeof(ulong)); return (uint) (pointerSize * (Count + 1)); } @@ -52,8 +56,8 @@ public uint GetPhysicalSize() /// public void Write(IBinaryStreamWriter writer) { - ulong imageBase = _owner.ImageBase; - bool is32Bit = _owner.Is32Bit; + ulong imageBase = _imageBase; + bool is32Bit = _is32Bit; for (int i = 0; i < Items.Count; i++) writer.WriteNativeInt(imageBase + Items[i].Rva, is32Bit); diff --git a/src/AsmResolver.PE/Tls/TlsDirectory.cs b/src/AsmResolver.PE/Tls/TlsDirectory.cs index fbe6c101c..b6bd730b1 100644 --- a/src/AsmResolver.PE/Tls/TlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/TlsDirectory.cs @@ -12,6 +12,8 @@ public class TlsDirectory : SegmentBase, ITlsDirectory { private readonly LazyVariable _templateData; private TlsCallbackCollection? _callbackFunctions; + private ulong _imageBase = 0x00400000; + private bool _is32Bit = true; /// /// Initializes a new empty TLS data directory. @@ -61,19 +63,12 @@ public TlsCharacteristics Characteristics set; } - /// - public ulong ImageBase - { - get; - set; - } = 0x00400000; - - /// - public bool Is32Bit + public override void UpdateOffsets(in RelocationParameters parameters) { - get; - set; - } = true; + _imageBase = parameters.ImageBase; + _is32Bit = parameters.Is32Bit; + base.UpdateOffsets(in parameters); + } /// /// Obtains the block of template data. @@ -96,29 +91,30 @@ public bool Is32Bit /// public IEnumerable GetRequiredBaseRelocations() { - int pointerSize = Is32Bit ? sizeof(uint) : sizeof(ulong); - var type = Is32Bit ? RelocationType.HighLow : RelocationType.Dir64; + int pointerSize = _is32Bit ? sizeof(uint) : sizeof(ulong); + var type = _is32Bit ? RelocationType.HighLow : RelocationType.Dir64; var result = new List(4 + CallbackFunctions.Count); for (int i = 0; i < 4; i++) result.Add(new BaseRelocation(type, this.ToReference(i * pointerSize))); for (int i = 0; i < CallbackFunctions.Count; i++) result.Add(new BaseRelocation(type, CallbackFunctions.ToReference(i * pointerSize))); + return result; } /// public override uint GetPhysicalSize() { - int pointerSize = Is32Bit ? sizeof(uint) : sizeof(ulong); + int pointerSize = _is32Bit ? sizeof(uint) : sizeof(ulong); return (uint) (pointerSize * 4 + 2 * sizeof(uint)); } /// public override void Write(IBinaryStreamWriter writer) { - ulong imageBase = ImageBase; - bool is32Bit = Is32Bit; + ulong imageBase = _imageBase; + bool is32Bit = _is32Bit; if (TemplateData is { } data) { diff --git a/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs b/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs index 72e200b43..a0dd01775 100644 --- a/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs @@ -162,8 +162,6 @@ public void Persistent(bool is32Bit) var directory = new TlsDirectory { - ImageBase = file.OptionalHeader.ImageBase, - Is32Bit = file.OptionalHeader.Magic == OptionalHeaderMagic.Pe32, TemplateData = templateData, Index = indexSegment.ToReference(), Characteristics = TlsCharacteristics.Align4Bytes, From 7e62f4e4033b154b2e3714def7c1a89c4b647ff3 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 20:20:57 +0200 Subject: [PATCH 104/182] Add CodeSegment.UpdateOffsets override. Remove image base argument in CodeSegment constructor usages. --- .../Builder/VTableFixups/VTableFixupsBuffer.cs | 2 +- .../Code/Native/NativeMethodBodySerializer.cs | 2 +- src/AsmResolver.PE/Code/CodeSegment.cs | 7 +++++++ src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs | 2 +- src/AsmResolver.PE/Platforms/Amd64Platform.cs | 4 ++-- src/AsmResolver.PE/Platforms/I386Platform.cs | 6 +++--- src/AsmResolver.PE/Platforms/Platform.cs | 3 +-- 7 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs b/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs index 44ceec89c..e0f512a63 100644 --- a/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs @@ -61,7 +61,7 @@ public void MapTokenToExport(UnmanagedExportInfo exportInfo, MetadataToken token vtableFixup.Tokens.Add(token); var vtableSymbol = new Symbol(vtableFixup.Tokens.GetReferenceToIndex(vtableFixup.Tokens.Count - 1)); - var thunkStub = _targetPlatform.CreateThunkStub(0x00400000, vtableSymbol); + var thunkStub = _targetPlatform.CreateThunkStub(vtableSymbol); // Register exported symbol. var stubReference = thunkStub.Segment.ToReference(); diff --git a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs index e4253b717..aed8247ec 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs @@ -18,7 +18,7 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont var provider = context.SymbolsProvider; // Create new raw code segment containing the native code. - var segment = new CodeSegment(provider.ImageBase, nativeMethodBody.Code); + var segment = new CodeSegment(nativeMethodBody.Code); // Process fixups. for (int i = 0; i < nativeMethodBody.AddressFixups.Count; i++) diff --git a/src/AsmResolver.PE/Code/CodeSegment.cs b/src/AsmResolver.PE/Code/CodeSegment.cs index eac4e00ef..b392e146e 100644 --- a/src/AsmResolver.PE/Code/CodeSegment.cs +++ b/src/AsmResolver.PE/Code/CodeSegment.cs @@ -56,6 +56,13 @@ public IList AddressFixups get; } = new List(); + /// + public override void UpdateOffsets(in RelocationParameters parameters) + { + ImageBase = parameters.ImageBase; + base.UpdateOffsets(in parameters); + } + /// public override uint GetPhysicalSize() => (uint) Code.Length; diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs index 9c19fbc71..56fb4fb7d 100644 --- a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs +++ b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs @@ -262,7 +262,7 @@ private static void CreateImportDirectory(IPEImage image, ManagedPEBuilderContex if (entrypointSymbol is null) throw new InvalidOperationException("Entrypoint symbol was required but not imported."); - context.Bootstrapper = context.Platform.CreateThunkStub(image.ImageBase, entrypointSymbol); + context.Bootstrapper = context.Platform.CreateThunkStub(entrypointSymbol); } } diff --git a/src/AsmResolver.PE/Platforms/Amd64Platform.cs b/src/AsmResolver.PE/Platforms/Amd64Platform.cs index 8ba103667..f0257da2f 100644 --- a/src/AsmResolver.PE/Platforms/Amd64Platform.cs +++ b/src/AsmResolver.PE/Platforms/Amd64Platform.cs @@ -25,9 +25,9 @@ public static Amd64Platform Instance public override bool IsClrBootstrapperRequired => false; /// - public override RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint) + public override RelocatableSegment CreateThunkStub(ISymbol entrypoint) { - var segment = new CodeSegment(imageBase, new byte[] + var segment = new CodeSegment(new byte[] { 0x48, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rex.w rex.b mov rax, [&symbol] 0xFF, 0xE0 // jmp [rax] diff --git a/src/AsmResolver.PE/Platforms/I386Platform.cs b/src/AsmResolver.PE/Platforms/I386Platform.cs index 15aa92d46..e5b6b0c4b 100644 --- a/src/AsmResolver.PE/Platforms/I386Platform.cs +++ b/src/AsmResolver.PE/Platforms/I386Platform.cs @@ -25,11 +25,11 @@ public static I386Platform Instance public override bool IsClrBootstrapperRequired => true; /// - public override RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint) + public override RelocatableSegment CreateThunkStub(ISymbol entrypoint) { - var segment = new CodeSegment(imageBase, new byte[] + var segment = new CodeSegment(new byte[] { - 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 // jmp [&symbol] + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 // jmp [&symbol] }); segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute32BitAddress, entrypoint)); diff --git a/src/AsmResolver.PE/Platforms/Platform.cs b/src/AsmResolver.PE/Platforms/Platform.cs index 506e6003d..554525644 100644 --- a/src/AsmResolver.PE/Platforms/Platform.cs +++ b/src/AsmResolver.PE/Platforms/Platform.cs @@ -69,10 +69,9 @@ or MachineType.Amd64DotNetFreeBsd /// /// Creates a new thunk stub that transfers control to the provided symbol. /// - /// The image base of the image. /// The symbol to jump to. /// The created stub. - public abstract RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint); + public abstract RelocatableSegment CreateThunkStub(ISymbol entrypoint); /// /// Attempts to extract the original RVA from the code at the provided thunk address reader. From e09103fe71455a22dbd3d1a50531aa188231f0c5 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 20:22:49 +0200 Subject: [PATCH 105/182] Remove unused INativeSymbolsProvider.ImageBase property. --- .../Builder/ManagedPEImageBuilder.cs | 2 +- .../Code/Native/INativeSymbolsProvider.cs | 8 -------- .../Code/Native/NativeSymbolsProvider.cs | 15 --------------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs index 66ff6f0a8..c67b17956 100644 --- a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs +++ b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs @@ -70,7 +70,7 @@ public PEImageBuildResult CreateImage(ModuleDefinition module) }; // Construct new .NET directory. - var symbolProvider = new NativeSymbolsProvider(image.ImageBase); + var symbolProvider = new NativeSymbolsProvider(); var result = DotNetDirectoryFactory.CreateDotNetDirectory( module, symbolProvider, diff --git a/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs index 30d0f2181..fa7dfac36 100644 --- a/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs +++ b/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs @@ -8,14 +8,6 @@ namespace AsmResolver.DotNet.Code.Native /// public interface INativeSymbolsProvider { - /// - /// Gets or sets the image base the final PE image is using. - /// - ulong ImageBase - { - get; - } - /// /// Adds a single symbol to the prototype. /// diff --git a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs index 9a1e97273..b09c1e09f 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs @@ -22,21 +22,6 @@ public class NativeSymbolsProvider : INativeSymbolsProvider private uint _maxExportedOrdinal = 0; private readonly List _floatingExportedSymbols = new(); - /// - /// Creates a new instance of the class. - /// - /// The image base of the final PE image. - public NativeSymbolsProvider(ulong imageBase) - { - ImageBase = imageBase; - } - - /// - public ulong ImageBase - { - get; - } - /// public ISymbol ImportSymbol(ISymbol symbol) { From 1b03c98ef3f4c5cf3291d29578abe41329de58ea Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 20:24:46 +0200 Subject: [PATCH 106/182] Remove image base arguments in CodeSegment tests. --- .../DotNet/Builder/MixedModeAssemblyTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs b/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs index dfc390f0a..0e957f84e 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs @@ -89,7 +89,7 @@ public void NativeBodyWithNoCalls() // Read image var image = PEImage.FromBytes(Properties.Resources.TheAnswer_NetFx); - ReplaceBodyWithNativeCode(image, new CodeSegment(image.ImageBase, new byte[] + ReplaceBodyWithNativeCode(image, new CodeSegment(new byte[] { 0xb8, 0x39, 0x05, 0x00, 0x00, // mov rax, 1337 0xc3 // ret @@ -120,7 +120,7 @@ public void NativeBodyWithCall() var function = new ImportedSymbol(0x4fc, "puts"); module.Symbols.Add(function); - var body = new CodeSegment(image.ImageBase, new byte[] + var body = new CodeSegment(new byte[] { /* 00: */ 0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28 /* 04: */ 0x48, 0x8D, 0x0D, 0x10, 0x00, 0x00, 0x00, // lea rcx, qword [rel str] @@ -170,7 +170,7 @@ public void NativeBodyWithCallX86() var function = new ImportedSymbol(0x4fc, "puts"); module.Symbols.Add(function); - var body = new CodeSegment(image.ImageBase, new byte[] + var body = new CodeSegment(new byte[] { /* 00: */ 0x55, // push ebp /* 01: */ 0x89, 0xE5, // mov ebp,esp From c00e8981befc5805c2a3e0cba307b758cbe86298 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:15:40 +0200 Subject: [PATCH 107/182] Add portable pdb table indices. --- .../DotNet/Metadata/Tables/TableIndex.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs index d9ca9e3bc..71d127152 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs @@ -53,7 +53,17 @@ public enum TableIndex : byte GenericParam = 42, MethodSpec = 43, GenericParamConstraint = 44, - Max = GenericParamConstraint + 1, + + Document = 0x30, + MethodDebugInformation = 0x31, + LocalScope = 0x32, + LocalVariable = 0x33, + LocalConstant = 0x34, + ImportScope = 0x35, + StateMachineMethod = 0x36, + CustomDebugInformation = 0x37, + + Max = CustomDebugInformation + 1, String = 0x70 } From c3da2f32079dfcfbf148cc3dab5016e332ea302c Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:15:52 +0200 Subject: [PATCH 108/182] Add DocumentRow structure. --- .../Metadata/Tables/Rows/DocumentRow.cs | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs new file mode 100644 index 000000000..8c5629006 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB Document metadata table. + /// + public struct DocumentRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Document metadata table. + /// + /// The index into the blob stream referencing the name of the document. + /// The index into the GUID stream referencing the hash algorithm identifier. + /// The index into the blob stream referencing the hash of the document. + /// The index into the GUID stream referencing the language identifier. + public DocumentRow(uint name, uint hashAlgorithm, uint hash, uint language) + { + Name = name; + HashAlgorithm = hashAlgorithm; + Hash = hash; + Language = language; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 4; + + /// + public uint this[int index] => index switch + { + 0 => Name, + 1 => HashAlgorithm, + 2 => Hash, + 3 => Language, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the blob stream referencing the name of the document. + /// + public uint Name + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the hash algorithm identifier. + /// + public uint HashAlgorithm + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the hash of the document. + /// + public uint Hash + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the language identifier. + /// + public uint Language + { + get; + set; + } + + /// + /// Reads a single Portable PDB Document row from an input stream. + /// + /// The input stream. + /// The layout of the assembly definition table. + /// The row. + public static DocumentRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new DocumentRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size), + reader.ReadIndex((IndexSize) layout.Columns[3].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Name, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(HashAlgorithm, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(Hash, (IndexSize) layout.Columns[2].Size); + writer.WriteIndex(Language, (IndexSize) layout.Columns[3].Size); + } + + /// + /// Determines whether this row is considered equal to the provided document row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(DocumentRow other) + { + return Name == other.Name + && HashAlgorithm == other.HashAlgorithm + && Hash == other.Hash + && Language == other.Language; + } + + /// + public override bool Equals(object? obj) => obj is DocumentRow other && Equals(other); + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Name; + hashCode = (hashCode * 397) ^ (int) HashAlgorithm; + hashCode = (hashCode * 397) ^ (int) Hash; + hashCode = (hashCode * 397) ^ (int) Language; + return hashCode; + } + } + + /// + public override string ToString() => $"({Name:X8}, {HashAlgorithm:X8}, {Hash:X8}, {Language:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} From 3a4f938d2bc98f7cf56603bfa4b2c09019573514 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:16:07 +0200 Subject: [PATCH 109/182] Add MethodDebugInformationRow structure. --- .../Tables/Rows/MethodDebugInformationRow.cs | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs new file mode 100644 index 000000000..4ffa591b9 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB Document metadata table. + /// + public struct MethodDebugInformationRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Method Debug Information metadata table. + /// + /// + /// The index into the Document table referencing the document that declares the method. + /// + /// + /// The index into the blob stream referencing an array of sequence points that make up the method. + /// + public MethodDebugInformationRow(uint document, uint sequencePoints) + { + Document = document; + SequencePoints = sequencePoints; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Document, + 1 => SequencePoints, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the Document table referencing the document that declares the method, or 0 + /// if the method does not have sequence points or spans multiple documents. + /// + public uint Document + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing an array of sequence points that make up the method, + /// or 0 if no sequence points are available. + /// + public uint SequencePoints + { + get; + set; + } + + /// + /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// + /// The input stream. + /// The layout of the assembly definition table. + /// The row. + public static MethodDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new MethodDebugInformationRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Document, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(SequencePoints, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided method debug information row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(MethodDebugInformationRow other) + { + return Document == other.Document && SequencePoints == other.SequencePoints; + } + + /// + public override bool Equals(object? obj) + { + return obj is MethodDebugInformationRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Document * 397) ^ (int) SequencePoints; + } + } + + public override string ToString() => $"({Document:X8}, {SequencePoints:X8})"; + + /// + public IEnumerator GetEnumerator() + { + return new MetadataRowColumnEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + } +} From e605bdbddc8d9c1d728d2bc1c5219b9b02e91add Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:16:22 +0200 Subject: [PATCH 110/182] Add LocalScopeRow structure. --- .../Metadata/Tables/Rows/LocalScopeRow.cs | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs new file mode 100644 index 000000000..4affddf8c --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local scope metadata table. + /// + public struct LocalScopeRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Scope metadata table. + /// + /// An index into the method table that defines the scope. + /// An index into the import scope table that defines the scope. + /// An index into the local variable table referencing the first local variable in the method. + /// An index into the local constant table referencing the first constant in the method. + /// The starting CIL offset of the scope. + /// The number of CIL bytes the scope spans. + public LocalScopeRow(uint method, uint importScope, uint variableList, uint constantList, uint startOffset, uint length) + { + Method = method; + ImportScope = importScope; + VariableList = variableList; + ConstantList = constantList; + StartOffset = startOffset; + Length = length; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 6; + + /// + public uint this[int index] => index switch + { + 0 => Method, + 1 => ImportScope, + 2 => VariableList, + 3 => ConstantList, + 4 => StartOffset, + 5 => Length, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the method table that defines the scope. + /// + public uint Method + { + get; + set; + } + + /// + /// Gets or sets an index into the import scope table that defines the scope. + /// + public uint ImportScope + { + get; + set; + } + + /// + /// Gets or sets an index into the local variable table referencing the first local variable in the method. + /// + public uint VariableList + { + get; + set; + } + + /// + /// Gets or sets an index into the local constant table referencing the first constant in the method. + /// + public uint ConstantList + { + get; + set; + } + + /// + /// Gets or sets The starting CIL offset of the scope. + /// + public uint StartOffset + { + get; + set; + } + + /// + /// Gets or sets the number of CIL bytes the scope spans. + /// + public uint Length + { + get; + set; + } + + /// + /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// + /// The input stream. + /// The layout of the assembly definition table. + /// The row. + public static LocalScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalScopeRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size), + reader.ReadIndex((IndexSize) layout.Columns[3].Size), + reader.ReadUInt32(), + reader.ReadUInt32()); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Method, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(ImportScope, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(VariableList, (IndexSize) layout.Columns[2].Size); + writer.WriteIndex(ConstantList, (IndexSize) layout.Columns[3].Size); + writer.WriteUInt32(StartOffset); + writer.WriteUInt32(Length); + } + + /// + /// Determines whether this row is considered equal to the provided local scope row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalScopeRow other) + { + return Method == other.Method + && ImportScope == other.ImportScope + && VariableList == other.VariableList + && ConstantList == other.ConstantList + && StartOffset == other.StartOffset + && Length == other.Length; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Method; + hashCode = (hashCode * 397) ^ (int) ImportScope; + hashCode = (hashCode * 397) ^ (int) VariableList; + hashCode = (hashCode * 397) ^ (int) ConstantList; + hashCode = (hashCode * 397) ^ (int) StartOffset; + hashCode = (hashCode * 397) ^ (int) Length; + return hashCode; + } + } + + /// + public override string ToString() + { + return $"({Method:X8}, {ImportScope:X8}, {VariableList:X8}, {ConstantList:X8}, {StartOffset:X8}, {Length:X8})"; + } + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + } +} From ff2824b835a5b6c84becf0e4de74039e56c0eea2 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:24:16 +0200 Subject: [PATCH 111/182] Add LocalVariableRow structure. --- .../Tables/Rows/LocalVariableAttributes.cs | 16 +++ .../Metadata/Tables/Rows/LocalVariableRow.cs | 128 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs new file mode 100644 index 000000000..503f2f555 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs @@ -0,0 +1,16 @@ +using System; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Provides members defining all possible flags that can be assigned to a local variable. + /// + [Flags] + public enum LocalVariableAttributes : ushort + { + /// + /// Indicates the local variable should be hidden in a debugger view. + /// + DebuggerHidden = 0x0001 + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs new file mode 100644 index 000000000..d92fb813e --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local variable metadata table. + /// + public struct LocalVariableRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Variable metadata table. + /// + /// The attributes associated to the local variable. + /// The index of the local variable. + /// An index into the strings stream referencing the name of the local variable. + public LocalVariableRow(LocalVariableAttributes attributes, ushort index, uint name) + { + Attributes = attributes; + Index = index; + Name = name; + } + + /// + public TableIndex TableIndex => TableIndex.LocalVariable; + + /// + public int Count => 3; + + /// + public uint this[int index] => index switch + { + 0 => (uint) Attributes, + 1 => Index, + 2 => Name, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets the attributes associated to the local variable. + /// + public LocalVariableAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the index of the local variable. + /// + public ushort Index + { + get; + set; + } + + /// + /// Gets or sets an index into the strings stream referencing the name of the local variable. + /// + public uint Name + { + get; + set; + } + + /// + /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// + /// The input stream. + /// The layout of the assembly definition table. + /// The row. + public static LocalVariableRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalVariableRow( + (LocalVariableAttributes) reader.ReadUInt16(), + reader.ReadUInt16(), + reader.ReadIndex((IndexSize) layout.Columns[2].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16(Index); + writer.WriteIndex(Name, (IndexSize) layout.Columns[2].Size); + } + + /// + /// Determines whether this row is considered equal to the provided local variable row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalVariableRow other) + { + return Attributes == other.Attributes + && Index == other.Index + && Name == other.Name; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Attributes; + hashCode = (hashCode * 397) ^ Index; + hashCode = (hashCode * 397) ^ (int) Name; + return hashCode; + } + } + + /// + public override string ToString() => $"({(ushort) Attributes:X4}, {Index:X4}, {Name:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} From acc16ba10317b603bd95ab82a50b01feb0203484 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:29:29 +0200 Subject: [PATCH 112/182] Add LocalConstant structure. --- .../Metadata/Tables/Rows/LocalConstantRow.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs new file mode 100644 index 000000000..fa7b34bf1 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local constant metadata table. + /// + public struct LocalConstantRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Constant metadata table. + /// + /// An index into the strings stream referencing the name of the constant. + /// An index into the blob stream referencing the signature of the constant. + public LocalConstantRow(uint name, uint signature) + { + Name = name; + Signature = signature; + } + + /// + public TableIndex TableIndex => TableIndex.LocalConstant; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Name, + 1 => Signature, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the strings stream referencing the name of the constant. + /// + public uint Name + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the signature of the constant. + /// + public uint Signature + { + get; + set; + } + + /// + /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// + /// The input stream. + /// The layout of the assembly definition table. + /// The row. + public static LocalConstantRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalConstantRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Name, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Signature, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided local constant row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalConstantRow other) + { + return Name == other.Name && Signature == other.Signature; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalConstantRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Name * 397) ^ (int) Signature; + } + } + + /// + public override string ToString() => $"({Name:X8}, {Signature:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} From 0a818e98ace403c9ce8c39022251897c68ee4c66 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:38:07 +0200 Subject: [PATCH 113/182] Add ImportScopeRow structure. --- .../Metadata/Tables/Rows/DocumentRow.cs | 2 +- .../Metadata/Tables/Rows/ImportScopeRow.cs | 112 ++++++++++++++++++ .../Metadata/Tables/Rows/LocalConstantRow.cs | 4 +- .../Metadata/Tables/Rows/LocalScopeRow.cs | 4 +- .../Metadata/Tables/Rows/LocalVariableRow.cs | 4 +- .../Tables/Rows/MethodDebugInformationRow.cs | 3 +- 6 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs index 8c5629006..0d8d8378b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs @@ -81,7 +81,7 @@ public uint Language /// Reads a single Portable PDB Document row from an input stream. /// /// The input stream. - /// The layout of the assembly definition table. + /// The layout of the document table. /// The row. public static DocumentRow FromReader(ref BinaryStreamReader reader, TableLayout layout) { diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs new file mode 100644 index 000000000..111d273ef --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB import scope metadata table. + /// + public struct ImportScopeRow : IMetadataRow + { + /// + ///Creates a new row for the Portable PDB import scope metadata table. + /// + /// + /// An index into the import parent scope defining the parent scope, or 0 if it is the root scope. + /// + /// + /// An index into the blob stream referencing the imports that this scope defines. + /// + public ImportScopeRow(uint parent, uint imports) + { + Parent = parent; + Imports = imports; + } + + /// + public TableIndex TableIndex => TableIndex.ImportScope; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Parent, + 1 => Imports, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the import parent scope defining the parent scope, or 0 if it is the root scope. + /// + public uint Parent + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the imports that this scope defines. + /// + public uint Imports + { + get; + set; + } + /// + /// Reads a single Portable PDB import scope row from an input stream. + /// + /// The input stream. + /// The layout of the import scope table. + /// The row. + public static ImportScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new ImportScopeRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Parent, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Imports, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided import scope row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(ImportScopeRow other) + { + return Parent == other.Parent && Imports == other.Imports; + } + + /// + public override bool Equals(object? obj) + { + return obj is ImportScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Parent * 397) ^ (int) Imports; + } + } + + /// + public override string ToString() => $"({Parent:X8}, {Imports:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs index fa7b34bf1..553195366 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs @@ -54,10 +54,10 @@ public uint Signature } /// - /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// Reads a single Portable PDB local constant row from an input stream. /// /// The input stream. - /// The layout of the assembly definition table. + /// The layout of the local constant table. /// The row. public static LocalConstantRow FromReader(ref BinaryStreamReader reader, TableLayout layout) { diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs index 4affddf8c..9fa9c387e 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs @@ -102,10 +102,10 @@ public uint Length } /// - /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// Reads a single Portable PDB local scope row from an input stream. /// /// The input stream. - /// The layout of the assembly definition table. + /// The layout of the local socpe table. /// The row. public static LocalScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout) { diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs index d92fb813e..1c27f04f0 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs @@ -66,10 +66,10 @@ public uint Name } /// - /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// Reads a single Portable PDB local variable row from an input stream. /// /// The input stream. - /// The layout of the assembly definition table. + /// The layout of the local variable table. /// The row. public static LocalVariableRow FromReader(ref BinaryStreamReader reader, TableLayout layout) { diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs index 4ffa591b9..9cfbb821b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs @@ -63,7 +63,7 @@ public uint SequencePoints /// Reads a single Portable PDB Method Debug Information row from an input stream. /// /// The input stream. - /// The layout of the assembly definition table. + /// The layout of the method debug information table. /// The row. public static MethodDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout) { @@ -104,6 +104,7 @@ public override int GetHashCode() } } + /// public override string ToString() => $"({Document:X8}, {SequencePoints:X8})"; /// From df9a7d2c69285ea8b00c2ac15d67eda17d2d57f0 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:42:39 +0200 Subject: [PATCH 114/182] Add StateMachineMethodRow structure. --- .../Metadata/Tables/Rows/ImportScopeRow.cs | 2 +- .../Tables/Rows/StateMachineMethodRow.cs | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs index 111d273ef..6badacd1c 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs @@ -11,7 +11,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows public struct ImportScopeRow : IMetadataRow { /// - ///Creates a new row for the Portable PDB import scope metadata table. + /// Creates a new row for the Portable PDB import scope metadata table. /// /// /// An index into the import parent scope defining the parent scope, or 0 if it is the root scope. diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs new file mode 100644 index 000000000..914125195 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB state machine method metadata table. + /// + public struct StateMachineMethodRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB state machine method metadata table. + /// + /// + /// An index into the method table referencing the MoveNext method of an async state machine. + /// + /// + /// An index into the method table referencing the kickoff method of an async state machine. + /// + public StateMachineMethodRow(uint moveNextMethod, uint kickoffMethod) + { + MoveNextMethod = moveNextMethod; + KickoffMethod = kickoffMethod; + } + + /// + public TableIndex TableIndex => TableIndex.StateMachineMethod; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => MoveNextMethod, + 1 => KickoffMethod, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the method table referencing the MoveNext method of an async state machine. + /// + public uint MoveNextMethod + { + get; + set; + } + + /// + /// Gets or sets an index into the method table referencing the kickoff method of an async state machine. + /// + public uint KickoffMethod + { + get; + set; + } + + /// + /// Reads a single Portable PDB state machine method row from an input stream. + /// + /// The input stream. + /// The layout of the state machine method table. + /// The row. + public static StateMachineMethodRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new StateMachineMethodRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(MoveNextMethod, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(KickoffMethod, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided state machine method row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(StateMachineMethodRow other) + { + return MoveNextMethod == other.MoveNextMethod && KickoffMethod == other.KickoffMethod; + } + + /// + public override bool Equals(object? obj) + { + return obj is StateMachineMethodRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) MoveNextMethod * 397) ^ (int) KickoffMethod; + } + } + + /// + public override string ToString() => $"({MoveNextMethod:X8}, {KickoffMethod:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} From a49e1f785aec69e6fe626d411495010176d186df Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 21:48:55 +0200 Subject: [PATCH 115/182] Add CustomDebugInformationRow structure. --- .../Tables/Rows/CustomDebugInformationRow.cs | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs new file mode 100644 index 000000000..1959a9233 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB custom debug information metadata table. + /// + public struct CustomDebugInformationRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB custom debug information metadata table. + /// + /// + /// A coded index defining the member that this debug information is associated to. + /// + /// + /// An index into the GUID stream referencing the type of debug data that is stored in this record. + /// + /// + /// An index into the blob stream referencing the data of the record. + /// + public CustomDebugInformationRow(uint parent, uint kind, uint value) + { + Parent = parent; + Kind = kind; + Value = value; + } + + /// + public TableIndex TableIndex => TableIndex.CustomDebugInformation; + + /// + public int Count => 3; + + /// + public uint this[int index] => index switch + { + 0 => Parent, + 1 => Kind, + 2 => Value, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets a coded index defining the member that this debug information is associated to. + /// + public uint Parent + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the type of debug data that is stored in this record. + /// + public uint Kind + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the data of the record. + /// + public uint Value + { + get; + set; + } + + /// + /// Reads a single Portable PDB custom debug information row from an input stream. + /// + /// The input stream. + /// The layout of the custom debug information table. + /// The row. + public static CustomDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new CustomDebugInformationRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Parent, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Kind, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(Value, (IndexSize) layout.Columns[2].Size); + } + + /// + /// Determines whether this row is considered equal to the provided custom debug information row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(CustomDebugInformationRow other) + { + return Parent == other.Parent && Kind == other.Kind && Value == other.Value; + } + + /// + public override bool Equals(object? obj) + { + return obj is CustomDebugInformationRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Parent; + hashCode = (hashCode * 397) ^ (int) Kind; + hashCode = (hashCode * 397) ^ (int) Value; + return hashCode; + } + } + + /// + public override string ToString() => $"({Parent:X8}, {Kind:X8}, {Value:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} From 57be35a9b9b18aa10fecd59faafb201ebce8bf70 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 20 Aug 2022 22:05:22 +0200 Subject: [PATCH 116/182] Add index encoders and table layouts. --- .../DotNet/Metadata/Tables/CodedIndex.cs | 32 +++++----- .../DotNet/Metadata/Tables/ColumnType.cs | 20 ++++-- .../Metadata/Tables/SerializedTableStream.cs | 26 +++++++- .../DotNet/Metadata/Tables/TablesStream.cs | 62 +++++++++++++++++-- 4 files changed, 113 insertions(+), 27 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs index 27f1fbc6a..45a0f0228 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs @@ -9,12 +9,12 @@ public enum CodedIndex /// Indicates the index is an index to a member in either the TypeRef, TypeDef or TypeSpec table. /// TypeDefOrRef = 45, - + /// /// Indicates the index is an index to a member in either the Field, Parameter or Property table. /// HasConstant, - + /// /// Indicates the index is an index to a member in one of the following tables: /// MethodDef, Field, TypeRef, TypeDef, Parameter, InterfaceImpl, MemberRef, Module, DeclSecurity, Property, Event, @@ -22,57 +22,59 @@ public enum CodedIndex /// GenericParamConstraint or MethodSpec. /// HasCustomAttribute, - + /// /// Indicates the index is an index to a member in either the Field or Parameter table. /// HasFieldMarshal, - + /// /// Indicates the index is an index to a member in either the TypeDef, MethodDef or Assembly table. /// HasDeclSecurity, - + /// /// Indicates the index is an index to a member in either the TypeDef, TypeRef, ModuleRef, MethodDef or TypeSpec /// table. /// MemberRefParent, - + /// /// Indicates the index is an index to a member in either the Event or Property table. /// HasSemantics, - + /// /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. /// MethodDefOrRef, - + /// /// Indicates the index is an index to a member in either the Field or MethodDef table. /// MemberForwarded, - + /// /// Indicates the index is an index to a member in either the File, AssemblyRef or ExportedType table. /// Implementation, - + /// - /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. + /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. /// CustomAttributeType, - + /// /// Indicates the index is an index to a member in either the Module, ModuleRef, AssemblyRef or TypeRef table. /// ResolutionScope, - + /// /// Indicates the index is an index to a member in either the TypeDef or MethodDef table. /// TypeOrMethodDef, + + HasCustomDebugInformation } - -} \ No newline at end of file + +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs index c24876da4..8ffb4e2d8 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs @@ -53,7 +53,16 @@ public enum ColumnType GenericParam = 42, MethodSpec = 43, GenericParamConstraint = 44, - + + Document = 0x30, + MethodDebugInformation = 0x31, + LocalScope = 0x32, + LocalVariable = 0x33, + LocalConstant = 0x34, + ImportScope = 0x35, + StateMachineMethod = 0x36, + CustomDebugInformation = 0x37, + TypeDefOrRef, HasConstant, HasCustomAttribute, @@ -67,13 +76,14 @@ public enum ColumnType CustomAttributeType, ResolutionScope, TypeOrMethodDef, - - String, + HasCustomDebugInformation, + + String, Blob, Guid, - + Byte = 0x8000001, UInt16 = 0x8000002, UInt32 = 0x8000004, } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs index f2a910eb8..16c3ecad2 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs @@ -75,7 +75,7 @@ private uint[] ReadRowCounts(ref BinaryStreamReader reader) { const TableIndex maxTableIndex = TableIndex.GenericParamConstraint; - var result = new uint[(int) maxTableIndex + 1 ]; + uint[] result = new uint[(int) maxTableIndex + 1]; for (TableIndex i = 0; i <= maxTableIndex; i++) result[(int) i] = HasTable(_validMask, i) ? reader.ReadUInt32() : 0; @@ -149,6 +149,15 @@ private IndexSize[] InitializeIndexSizes() // TypeOrMethodDef GetCodedIndexSize(TableIndex.TypeDef, TableIndex.Method), + + // HasCustomDebugInformation + GetCodedIndexSize(TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, + TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, + TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig + , TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, + TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, + TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) }); return result.ToArray(); @@ -165,10 +174,10 @@ private IndexSize GetCodedIndexSize(params TableIndex[] tables) } /// - protected override IList GetTables() + protected override IList GetTables() { uint offset = _headerSize; - var tables = new IMetadataTable[] + var tables = new IMetadataTable?[] { CreateNextTable(TableIndex.Module, ref offset, ModuleDefinitionRow.FromReader), CreateNextTable(TableIndex.TypeRef, ref offset, TypeReferenceRow.FromReader), @@ -215,6 +224,17 @@ protected override IList GetTables() CreateNextTable(TableIndex.GenericParam, ref offset, GenericParameterRow.FromReader), CreateNextTable(TableIndex.MethodSpec, ref offset, MethodSpecificationRow.FromReader), CreateNextTable(TableIndex.GenericParamConstraint, ref offset, GenericParameterConstraintRow.FromReader), + null, + null, + null, + CreateNextTable(TableIndex.Document, ref offset, DocumentRow.FromReader), + CreateNextTable(TableIndex.MethodDebugInformation, ref offset, MethodDebugInformationRow.FromReader), + CreateNextTable(TableIndex.LocalScope, ref offset, LocalScopeRow.FromReader), + CreateNextTable(TableIndex.LocalVariable, ref offset, LocalVariableRow.FromReader), + CreateNextTable(TableIndex.LocalConstant, ref offset, LocalConstantRow.FromReader), + CreateNextTable(TableIndex.ImportScope, ref offset, ImportScopeRow.FromReader), + CreateNextTable(TableIndex.StateMachineMethod, ref offset, StateMachineMethodRow.FromReader), + CreateNextTable(TableIndex.CustomDebugInformation, ref offset, CustomDebugInformationRow.FromReader), }; _tablesInitialized = true; return tables; diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index d8f673e46..256b7c1ad 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -387,10 +387,10 @@ protected static bool IsSorted(ulong sortedMask, TableIndex table) /// /// This method is called upon initialization of the property. /// - protected virtual IList GetTables() + protected virtual IList GetTables() { var layouts = TableLayouts; - return new IMetadataTable[] + return new IMetadataTable?[] { new MetadataTable(TableIndex.Module, layouts[0]), new MetadataTable(TableIndex.TypeRef, layouts[1]), @@ -437,6 +437,17 @@ protected virtual IList GetTables() new MetadataTable(TableIndex.GenericParam, layouts[42]), new MetadataTable(TableIndex.MethodSpec, layouts[43]), new MetadataTable(TableIndex.GenericParamConstraint, layouts[44]), + null, + null, + null, + new MetadataTable(TableIndex.Document, layouts[48]), + new MetadataTable(TableIndex.MethodDebugInformation, layouts[49]), + new MetadataTable(TableIndex.LocalScope, layouts[50]), + new MetadataTable(TableIndex.LocalVariable, layouts[51]), + new MetadataTable(TableIndex.LocalConstant, layouts[52]), + new MetadataTable(TableIndex.ImportScope, layouts[53]), + new MetadataTable(TableIndex.StateMachineMethod, layouts[54]), + new MetadataTable(TableIndex.CustomDebugInformation, layouts[55]), }; } @@ -475,7 +486,15 @@ protected virtual IList GetTables() [CodedIndex.ResolutionScope] = new(this, TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef), [CodedIndex.TypeOrMethodDef] = new(this, - TableIndex.TypeDef, TableIndex.Method) + TableIndex.TypeDef, TableIndex.Method), + [CodedIndex.HasCustomDebugInformation] = new(this, + TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, TableIndex.Param, + TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, TableIndex.DeclSecurity, + TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, TableIndex.ModuleRef, + TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, TableIndex.File, + TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, + TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) }; } @@ -526,7 +545,7 @@ protected virtual uint GetColumnSize(ColumnType columnType) { if (_layouts.IsInitialized) { - if (columnType <= ColumnType.GenericParamConstraint) + if (columnType <= ColumnType.Document) return (uint) Tables[(int) columnType].IndexSize; if (columnType <= ColumnType.TypeOrMethodDef) return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize; @@ -744,6 +763,41 @@ protected TableLayout[] GetTableLayouts() new TableLayout( new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)), new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + default, + default, + default, + new TableLayout( + new ColumnLayout("Name", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("HashAlgorithm", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("Hash", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Language", ColumnType.Guid, GuidIndexSize)), + new TableLayout( + new ColumnLayout("Document", ColumnType.Document, GetColumnSize(ColumnType.Document)), + new ColumnLayout("SequencePoints", ColumnType.Blob, BlobIndexSize)), + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("ImportScope", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), + new ColumnLayout("VariableList", ColumnType.LocalVariable, GetColumnSize(ColumnType.LocalVariable)), + new ColumnLayout("ConstantList", ColumnType.LocalConstant, GetColumnSize(ColumnType.LocalConstant)), + new ColumnLayout("StartOffset", ColumnType.UInt32), + new ColumnLayout("Length", ColumnType.UInt32)), + new TableLayout( + new ColumnLayout("Attributes", ColumnType.UInt16), + new ColumnLayout("Index", ColumnType.UInt16), + new ColumnLayout("VariableList", ColumnType.String, StringIndexSize)), + new TableLayout( + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + new TableLayout( + new ColumnLayout("Parent", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), + new ColumnLayout("Imports", ColumnType.Blob, BlobIndexSize)), + new TableLayout( + new ColumnLayout("MoveNextMethod", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("KickoffMethod", ColumnType.Method, GetColumnSize(ColumnType.Method))), + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasCustomDebugInformation, GetColumnSize(ColumnType.HasCustomDebugInformation)), + new ColumnLayout("Kind", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), }; return result; From 5b212496fa9cf18cf980baae21a43bd9d2af0e16 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 21 Aug 2022 18:15:33 +0200 Subject: [PATCH 117/182] Update docs on new API changes in TLS directory. --- docs/peimage/tls.rst | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/peimage/tls.rst b/docs/peimage/tls.rst index 564b25600..ad2f97ac0 100644 --- a/docs/peimage/tls.rst +++ b/docs/peimage/tls.rst @@ -6,7 +6,7 @@ Executables that use multiple threads might require static (non-stack) memory th All code relevant to the TLS data directory of a PE resides in the following namespace: .. code-block:: csharp - + using AsmResolver.PE.Tls; @@ -21,7 +21,7 @@ The PE file format defines a segment of memory within the TLS data directory tha .. code-block:: csharp - + var indexSegment = new DataSegment(new byte[8]); var directory = new TlsDirectory @@ -48,18 +48,14 @@ Next to static initialization data, it is also possible to specify a list of fun Creating new TLS directories ---------------------------- -Since the TLS data directory stores its data using virtual addresses (VA) rather than relative virtual addresses (RVA), AsmResolver requires the image base as well as the pointer size. This is done through the ``ImageBase`` and ``Is32Bit`` properties. By default, the following values are assumed: +Adding a new TLS directory to an image can be done using the parameterless constructor of the ``TlsDirectory`` class: .. code-block:: csharp - var directory = new TlsDirectory(); - directory.ImageBase = 0x00400000; - directory.Is32Bit = true; - - -Typically, you should make sure they are in sync with the values found in the file and optional header of the final PE file. Upon reading from an existing PE file, these two properties are initialized to the values stored in these two headers. + var tlsDirectory = new TlsDirectory(); + image.TlsDirectory = tlsDirectory; -When building a relocatable PE file, you might also need to add base address relocations to the VAs inside the TLS directory. To quickly get all the base relocations required, use the ``GetRequiredBaseRelocations`` method: +A TLS directory references all its sub segments using virtual addresses (VA) rather than relative addresses (RVA). This means that constructing a relocatable PE image with a TLS directory requires base relocation entries to be registered that let the Windows PE loader rebase all virtual addresses used in the directory when necessary. To quickly register all the required base relocations, you can call the ``GetRequiredBaseRelocations`` method and add all returned entries to the base relocation directory of the PE image: .. code-block:: csharp @@ -67,5 +63,5 @@ When building a relocatable PE file, you might also need to add base address rel IPEImage image = ...; - foreach (var relocation in image.TlsDirectory.GetRequiredBaseRelocations()) - image.Relocations.Add(relocation); \ No newline at end of file + foreach (var relocation in tlsDirectory.GetRequiredBaseRelocations()) + image.Relocations.Add(relocation); From aa159cdf4d1ea134d9b16677339d7cdcab604955 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 21 Aug 2022 18:30:23 +0200 Subject: [PATCH 118/182] Make relocation parameters mutable to avoid a lot of copying. --- src/AsmResolver.PE.File/PEFile.cs | 2 +- .../DotNet/Cil/CilRawFatMethodBody.cs | 8 +++-- .../DotNet/Cil/CilRawTinyMethodBody.cs | 2 +- .../Exports/Builder/ExportDirectoryBuffer.cs | 2 +- .../Builder/ImportAddressDirectoryBuffer.cs | 2 +- .../Imports/Builder/ImportDirectoryBuffer.cs | 4 +-- src/AsmResolver/RelocationParameters.cs | 30 +++++++++++++------ src/AsmResolver/SegmentBuilder.cs | 6 ++-- 8 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index 73cc0b9cf..6ce7616e2 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -415,7 +415,7 @@ public void AlignSections() var section = Sections[i]; section.UpdateOffsets(relocation); - relocation = relocation.Advance( + relocation.Advance( section.GetPhysicalSize().Align(OptionalHeader.FileAlignment), section.GetVirtualSize().Align(OptionalHeader.SectionAlignment)); } diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs index a803455c0..2d22d134a 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs @@ -176,17 +176,19 @@ public override void UpdateOffsets(in RelocationParameters parameters) { base.UpdateOffsets(parameters); - var current = parameters.Advance(12); + var current = parameters.WithAdvance(12); + Code.UpdateOffsets(current); if (HasSections) { uint codeSize = Code.GetPhysicalSize(); - current = current.Advance(codeSize).Align(4); + current.Advance(codeSize); + current.Align(4); for (int i = 0; i < ExtraSections.Count; i++) { ExtraSections[i].UpdateOffsets(current); - current = current.Advance(ExtraSections[i].GetPhysicalSize()); + current.Advance(ExtraSections[i].GetPhysicalSize()); } } } diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs index e4532f00c..852af652b 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs @@ -66,7 +66,7 @@ public CilRawTinyMethodBody(IReadableSegment code) public override void UpdateOffsets(in RelocationParameters parameters) { base.UpdateOffsets(parameters); - Code.UpdateOffsets(parameters.Advance(1)); + Code.UpdateOffsets(parameters.WithAdvance(1)); } /// diff --git a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs index fc7e4ebac..8409daf39 100644 --- a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs @@ -86,7 +86,7 @@ public void AddDirectory(IExportDirectory exportDirectory) public override void UpdateOffsets(in RelocationParameters parameters) { base.UpdateOffsets(parameters); - _contentsBuilder.UpdateOffsets(parameters.Advance(ExportDirectoryHeaderSize)); + _contentsBuilder.UpdateOffsets(parameters.WithAdvance(ExportDirectoryHeaderSize)); } /// diff --git a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs index 6565ba4a1..8b24c41d0 100644 --- a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs @@ -29,7 +29,7 @@ public override void UpdateOffsets(in RelocationParameters parameters) var thunkTable = GetModuleThunkTable(module); uint size = thunkTable.GetPhysicalSize(); thunkTable.UpdateOffsets(current); - current = current.Advance(size); + current.Advance(size); } } diff --git a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs index cd61d5447..5bf7adb61 100644 --- a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs @@ -46,7 +46,7 @@ public override void UpdateOffsets(in RelocationParameters parameters) { base.UpdateOffsets(parameters); - var current = parameters.Advance(_entriesLength); + var current = parameters.WithAdvance(_entriesLength); foreach (var module in Modules) { @@ -54,7 +54,7 @@ public override void UpdateOffsets(in RelocationParameters parameters) uint size = thunkTable.GetPhysicalSize(); thunkTable.UpdateOffsets(current); - current = current.Advance(size); + current.Advance(size); } HintNameTable.UpdateOffsets(current); diff --git a/src/AsmResolver/RelocationParameters.cs b/src/AsmResolver/RelocationParameters.cs index c6d3f546e..3ef4f5ad0 100644 --- a/src/AsmResolver/RelocationParameters.cs +++ b/src/AsmResolver/RelocationParameters.cs @@ -3,7 +3,7 @@ namespace AsmResolver /// /// Provides parameters for relocating a segment to a new offset-rva pair. /// - public readonly struct RelocationParameters + public struct RelocationParameters { /// /// Creates new relocation parameters. @@ -44,6 +44,7 @@ public ulong ImageBase public ulong Offset { get; + set; } /// @@ -52,6 +53,7 @@ public ulong Offset public uint Rva { get; + set; } /// @@ -72,7 +74,6 @@ public bool Is32Bit /// /// The new offset. /// The new relative virtual address. - /// The new relocation parameters. public RelocationParameters WithOffsetRva(ulong offset, uint rva) { return new RelocationParameters(ImageBase, offset, rva, Is32Bit); @@ -82,10 +83,20 @@ public RelocationParameters WithOffsetRva(ulong offset, uint rva) /// Aligns the current offset and virtual address to the nearest multiple of the provided alignment. /// /// The alignment. + public void Align(uint alignment) + { + Offset = Offset.Align(alignment); + Rva = Rva.Align(alignment); + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance with. /// The new relocation parameters. - public RelocationParameters Align(uint alignment) + public readonly RelocationParameters WithAdvance(uint count) { - return WithOffsetRva(Offset.Align(alignment), Rva.Align(alignment)); + return new RelocationParameters(ImageBase, Offset + count, Rva + count, Is32Bit); } /// @@ -93,9 +104,10 @@ public RelocationParameters Align(uint alignment) /// /// The number of bytes to advance with. /// The new relocation parameters. - public RelocationParameters Advance(uint count) + public void Advance(uint count) { - return WithOffsetRva(Offset + count, Rva + count); + Offset += count; + Rva += count; } /// @@ -103,10 +115,10 @@ public RelocationParameters Advance(uint count) /// /// The number of bytes to advance the physical offset with. /// The number of bytes to advance the virtual address with. - /// The new relocation parameters. - public RelocationParameters Advance(uint physicalCount, uint virtualCount) + public void Advance(uint physicalCount, uint virtualCount) { - return WithOffsetRva(Offset + physicalCount, Rva + virtualCount); + Offset += physicalCount; + Rva += virtualCount; } /// diff --git a/src/AsmResolver/SegmentBuilder.cs b/src/AsmResolver/SegmentBuilder.cs index 09fcb0fed..1bfd17eb3 100644 --- a/src/AsmResolver/SegmentBuilder.cs +++ b/src/AsmResolver/SegmentBuilder.cs @@ -63,17 +63,17 @@ public void UpdateOffsets(in RelocationParameters parameters) var current = parameters; foreach (var item in _items) { - current = current.Align(item.Alignment); + current.Align(item.Alignment); item.Segment.UpdateOffsets(current); uint physicalSize = item.Segment.GetPhysicalSize(); uint virtualSize = item.Segment.GetVirtualSize(); - current = current.Advance(physicalSize, virtualSize); + current.Advance(physicalSize, virtualSize); } _physicalSize = (uint) (current.Offset - parameters.Offset); - _virtualSize = (uint) (current.Rva - parameters.Rva); + _virtualSize = current.Rva - parameters.Rva; } /// From 32d2aeeed8b65096b8f7505dd6311632167809a5 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 21 Aug 2022 18:31:34 +0200 Subject: [PATCH 119/182] Remove redundant SegmentReference.CanUpdateOffsets --- src/AsmResolver/SegmentReference.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/AsmResolver/SegmentReference.cs b/src/AsmResolver/SegmentReference.cs index b14135904..ecda7b3e2 100644 --- a/src/AsmResolver/SegmentReference.cs +++ b/src/AsmResolver/SegmentReference.cs @@ -32,9 +32,6 @@ public SegmentReference(ISegment? segment) /// public uint Rva => Segment?.Rva ?? 0; - /// - public bool CanUpdateOffsets => Segment?.CanUpdateOffsets ?? false; - /// public bool IsBounded => true; From 591f610e2a542d9ee952c6cd8231d7afb03d3d0e Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 21 Aug 2022 19:16:34 +0200 Subject: [PATCH 120/182] Add ITypeDefOrRef.ToTypeSignature overload taking isValueType parameter, avoiding expensive resolutions. --- src/AsmResolver.DotNet/ITypeDefOrRef.cs | 13 +++++++++++++ src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs | 2 ++ src/AsmResolver.DotNet/TypeDefinition.cs | 7 +++++-- .../TypeDescriptorExtensions.cs | 19 ++++++++++++++++++- src/AsmResolver.DotNet/TypeReference.cs | 7 +++++-- src/AsmResolver.DotNet/TypeSpecification.cs | 3 +++ 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/AsmResolver.DotNet/ITypeDefOrRef.cs b/src/AsmResolver.DotNet/ITypeDefOrRef.cs index a81bc335b..61701cde0 100644 --- a/src/AsmResolver.DotNet/ITypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/ITypeDefOrRef.cs @@ -1,3 +1,5 @@ +using AsmResolver.DotNet.Signatures.Types; + namespace AsmResolver.DotNet { /// @@ -35,5 +37,16 @@ public interface ITypeDefOrRef : ITypeDescriptor, IMemberRefParent, IHasCustomAt /// The reference importer to use for importing the type. /// The imported type. new ITypeDefOrRef ImportWith(ReferenceImporter importer); + + /// + /// Transforms the type descriptor to an instance of a , which can be used in + /// blob signatures. + /// + /// true if the type is a value type, false otherwise. + /// The constructed type signature instance. + /// + /// This function can be used to avoid type resolution on type references. + /// + TypeSignature ToTypeSignature(bool isValueType); } } diff --git a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs index aa0dc667d..6197f4085 100644 --- a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs @@ -90,6 +90,8 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error) TypeSignature ITypeDescriptor.ToTypeSignature() => throw new InvalidOperationException(); + TypeSignature ITypeDefOrRef.ToTypeSignature(bool isValueType) => throw new InvalidOperationException(); + /// public override string ToString() => ((IFullNameProvider) this).Name!; diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index ce14eb497..faecf8fbd 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -674,10 +674,13 @@ public bool Implements(string fullName) ITypeDefOrRef ITypeDescriptor.ToTypeDefOrRef() => this; /// - public TypeSignature ToTypeSignature() + public TypeSignature ToTypeSignature() => ToTypeSignature(IsValueType); + + /// + public TypeSignature ToTypeSignature(bool isValueType) { return Module?.CorLibTypeFactory.FromType(this) as TypeSignature - ?? new TypeDefOrRefSignature(this, IsValueType); + ?? new TypeDefOrRefSignature(this, isValueType); } /// diff --git a/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs b/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs index a108f6376..360ad9f54 100644 --- a/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs +++ b/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs @@ -100,7 +100,24 @@ public static class TypeDescriptorExtensions public static GenericInstanceTypeSignature MakeGenericInstanceType( this ITypeDescriptor type, params TypeSignature[] typeArguments) { - return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), type.IsValueType, typeArguments); + return type.MakeGenericInstanceType(type.IsValueType, typeArguments); + } + + /// + /// Constructs a new pointer type signature with the provided type descriptor as element type. + /// as element type. + /// + /// The element type. + /// true if the type is a value type, false otherwise. + /// The arguments to instantiate the type with. + /// The constructed by-reference type signature. + /// + /// This function can be used to avoid type resolution on type references. + /// + public static GenericInstanceTypeSignature MakeGenericInstanceType( + this ITypeDescriptor type, bool isValueType, params TypeSignature[] typeArguments) + { + return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), isValueType, typeArguments); } /// diff --git a/src/AsmResolver.DotNet/TypeReference.cs b/src/AsmResolver.DotNet/TypeReference.cs index 0b1332fe9..fef21335e 100644 --- a/src/AsmResolver.DotNet/TypeReference.cs +++ b/src/AsmResolver.DotNet/TypeReference.cs @@ -130,10 +130,13 @@ public IList CustomAttributes ITypeDefOrRef ITypeDescriptor.ToTypeDefOrRef() => this; /// - public TypeSignature ToTypeSignature() + public TypeSignature ToTypeSignature() => ToTypeSignature(IsValueType); + + /// + public TypeSignature ToTypeSignature(bool isValueType) { return Module?.CorLibTypeFactory.FromType(this) as TypeSignature - ?? new TypeDefOrRefSignature(this, IsValueType); + ?? new TypeDefOrRefSignature(this, isValueType); } /// diff --git a/src/AsmResolver.DotNet/TypeSpecification.cs b/src/AsmResolver.DotNet/TypeSpecification.cs index e8d805b4d..aa4420351 100644 --- a/src/AsmResolver.DotNet/TypeSpecification.cs +++ b/src/AsmResolver.DotNet/TypeSpecification.cs @@ -95,6 +95,9 @@ public IList CustomAttributes public TypeSignature ToTypeSignature() => Signature ?? throw new ArgumentException("Signature embedded into the type specification is null."); + /// + public TypeSignature ToTypeSignature(bool isValueType) => ToTypeSignature(); + /// public bool IsImportedInModule(ModuleDefinition module) => Signature?.IsImportedInModule(module) ?? false; From a45279bbbea8249e237faa3d52daa61ce37f9c34 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 22 Aug 2022 16:18:20 +0200 Subject: [PATCH 121/182] Update index encoders and token allocators to allow for pdb md table indices. --- src/AsmResolver.DotNet/TokenAllocator.cs | 3 ++ .../DotNet/Metadata/Tables/CodedIndex.cs | 9 +++++- .../DotNet/Metadata/Tables/ColumnType.cs | 5 +++ .../DotNet/Metadata/Tables/MetadataToken.cs | 2 +- .../Metadata/Tables/SerializedTableStream.cs | 11 ++++--- .../DotNet/Metadata/Tables/TableIndex.cs | 1 + .../Metadata/Tables/TableIndexExtensions.cs | 19 ++++++++++++ .../DotNet/Metadata/Tables/TablesStream.cs | 31 ++++++++++++------- .../TypeSpecificationTest.cs | 22 ++++++------- 9 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs diff --git a/src/AsmResolver.DotNet/TokenAllocator.cs b/src/AsmResolver.DotNet/TokenAllocator.cs index f02d606df..9f1cff05a 100644 --- a/src/AsmResolver.DotNet/TokenAllocator.cs +++ b/src/AsmResolver.DotNet/TokenAllocator.cs @@ -38,6 +38,9 @@ private void InitializeTable(IDotNetDirectory netDirectory) var tableStream = netDirectory.Metadata!.GetStream(); for (TableIndex index = 0; index < TableIndex.Max; index++) { + if (!index.IsValidTableIndex()) + continue; + var table = tableStream.GetTable(index); _buckets[(int) index] = new TokenBucket(new MetadataToken(index, (uint) table.Count + 1)); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs index 45a0f0228..8817f38dc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs @@ -8,7 +8,7 @@ public enum CodedIndex /// /// Indicates the index is an index to a member in either the TypeRef, TypeDef or TypeSpec table. /// - TypeDefOrRef = 45, + TypeDefOrRef = ColumnType.TypeDefOrRef, /// /// Indicates the index is an index to a member in either the Field, Parameter or Property table. @@ -74,6 +74,13 @@ public enum CodedIndex /// TypeOrMethodDef, + /// + /// Indicates the index is an index to a member in one of the following tables: + /// MethodDef, Field, TypeRef, TypeDef, Parameter, InterfaceImpl, MemberRef, Module, DeclSecurity, Property, + /// Event, StandAloneSig, ModuleRef, TypeSpec, Assembly, AssemblyRef, File, ExportedType, ManifestResource, + /// GenericParam, GenericParamConstraint, MethodSpec, Document, LocalScope, LocalVariable, LocalConstant, + /// or ImportScope + /// HasCustomDebugInformation } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs index 8ffb4e2d8..5e794615d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs @@ -8,6 +8,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// public enum ColumnType { + // Normal type system indices (in sync with TableIndex). Module = 0, TypeRef = 1, TypeDef = 2, @@ -54,6 +55,7 @@ public enum ColumnType MethodSpec = 43, GenericParamConstraint = 44, + // PortablePDB indices (in sync with TableIndex). Document = 0x30, MethodDebugInformation = 0x31, LocalScope = 0x32, @@ -63,6 +65,7 @@ public enum ColumnType StateMachineMethod = 0x36, CustomDebugInformation = 0x37, + // Coded indices (in sync with CodedIndex). TypeDefOrRef, HasConstant, HasCustomAttribute, @@ -78,10 +81,12 @@ public enum ColumnType TypeOrMethodDef, HasCustomDebugInformation, + // Heap indices. String, Blob, Guid, + // Primitives. Byte = 0x8000001, UInt16 = 0x8000002, UInt32 = 0x8000004, diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs index 56d79aa17..a28bd44fb 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs @@ -10,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Represents the zero metadata token, or the absence of a metadata token. /// - public static readonly MetadataToken Zero = new MetadataToken(0); + public static readonly MetadataToken Zero = new(0); /// /// Converts a 32-bit integer to a metadata token. diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs index 16c3ecad2..04ba90cd4 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs @@ -73,11 +73,14 @@ public SerializedTableStream(PEReaderContext context, string name, in BinaryStre private uint[] ReadRowCounts(ref BinaryStreamReader reader) { - const TableIndex maxTableIndex = TableIndex.GenericParamConstraint; + uint[] result = new uint[(int) TableIndex.Max]; - uint[] result = new uint[(int) maxTableIndex + 1]; - for (TableIndex i = 0; i <= maxTableIndex; i++) - result[(int) i] = HasTable(_validMask, i) ? reader.ReadUInt32() : 0; + for (TableIndex i = 0; i <= TableIndex.GenericParamConstraint; i++) + { + result[(int) i] = HasTable(_validMask, i) + ? reader.ReadUInt32() + : 0; + } return result; } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs index 71d127152..174b67e24 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs @@ -67,4 +67,5 @@ public enum TableIndex : byte String = 0x70 } + } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs new file mode 100644 index 000000000..e9573dfb2 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs @@ -0,0 +1,19 @@ +namespace AsmResolver.PE.DotNet.Metadata.Tables; + +/// +/// Provides extension methods to the enumeration. +/// +public static class TableIndexExtensions +{ + /// + /// Determines whether the provided index is a valid table index. + /// + /// The index. + /// true if valid, false otherwise. + public static bool IsValidTableIndex(this TableIndex index) + { + return index is >= TableIndex.Module and <= TableIndex.GenericParamConstraint + or >= TableIndex.Document and <= TableIndex.CustomDebugInformation + or TableIndex.String; + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index 256b7c1ad..e60803176 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -11,6 +11,8 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// public class TablesStream : SegmentBase, IMetadataStream { + private const TableIndex MaxTypeSystemTableIndex = TableIndex.GenericParamConstraint; + /// /// The default name of a table stream using the compressed format. /// @@ -32,7 +34,7 @@ public class TablesStream : SegmentBase, IMetadataStream public const string UncompressedStreamName = "#Schema"; private readonly Dictionary _indexEncoders; - private readonly LazyVariable> _tables; + private readonly LazyVariable> _tables; private readonly LazyVariable> _layouts; /// @@ -41,7 +43,7 @@ public class TablesStream : SegmentBase, IMetadataStream public TablesStream() { _layouts = new LazyVariable>(GetTableLayouts); - _tables = new LazyVariable>(GetTables); + _tables = new LazyVariable>(GetTables); _indexEncoders = CreateIndexEncoders(); } @@ -199,7 +201,7 @@ public uint ExtraData /// This collection always contains all tables, in the same order as defines, regardless /// of whether a table actually has elements or not. /// - protected IList Tables => _tables.Value; + protected IList Tables => _tables.Value; /// /// Gets the layout of all tables in the stream. @@ -220,7 +222,7 @@ protected void SynchronizeTableLayoutsWithFlags() { var layouts = GetTableLayouts(); for (int i = 0; i < Tables.Count; i++) - Tables[i].UpdateTableLayout(layouts[i]); + Tables[i]?.UpdateTableLayout(layouts[i]); } /// @@ -274,7 +276,7 @@ protected virtual ulong ComputeValidBitmask() ulong result = 0; for (int i = 0; i < Tables.Count; i++) { - if (Tables[i].Count > 0) + if (Tables[i]?.Count > 0) result |= 1UL << i; } @@ -301,7 +303,7 @@ protected virtual ulong ComputeSortedBitmask() protected virtual int GetTablesCount(ulong validBitmask) { int count = 0; - for (TableIndex i = 0; i < (TableIndex) Tables.Count; i++) + for (TableIndex i = 0; i <= TableIndex.GenericParamConstraint; i++) { if (HasTable(validBitmask, i)) count++; @@ -337,7 +339,7 @@ protected virtual uint GetTablesSize(ulong validBitmask) /// The valid bitmask, indicating all present tables in the stream. protected virtual void WriteRowCounts(IBinaryStreamWriter writer, ulong validBitmask) { - for (TableIndex i = 0; i < (TableIndex) Tables.Count; i++) + for (TableIndex i = 0; i <= MaxTypeSystemTableIndex; i++) { if (HasTable(validBitmask, i)) writer.WriteInt32(GetTable(i).Count); @@ -503,7 +505,8 @@ protected static bool IsSorted(ulong sortedMask, TableIndex table) /// /// The table index. /// The table. - public virtual IMetadataTable GetTable(TableIndex index) => Tables[(int) index]; + public virtual IMetadataTable GetTable(TableIndex index) => + Tables[(int) index] ?? throw new ArgumentOutOfRangeException(nameof(index)); /// /// Gets a table by its row type. @@ -525,7 +528,7 @@ public virtual MetadataTable GetTable() public virtual MetadataTable GetTable(TableIndex index) where TRow : struct, IMetadataRow { - return (MetadataTable) Tables[(int) index]; + return (MetadataTable) (Tables[(int) index] ?? throw new ArgumentOutOfRangeException(nameof(index))); } private IndexSize GetStreamIndexSize(int bitIndex) => (IndexSize) (((((int) Flags >> bitIndex) & 1) + 1) * 2); @@ -545,9 +548,13 @@ protected virtual uint GetColumnSize(ColumnType columnType) { if (_layouts.IsInitialized) { - if (columnType <= ColumnType.Document) - return (uint) Tables[(int) columnType].IndexSize; - if (columnType <= ColumnType.TypeOrMethodDef) + if (columnType <= ColumnType.CustomDebugInformation) + { + return (uint) (Tables[(int) columnType]?.IndexSize + ?? throw new ArgumentOutOfRangeException(nameof(columnType))); + } + + if (columnType <= ColumnType.HasCustomDebugInformation) return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize; } diff --git a/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs b/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs index d763cc1c4..ee2e8c2e9 100644 --- a/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs @@ -12,7 +12,7 @@ namespace AsmResolver.DotNet.Tests { public class TypeSpecificationTest { - + [Fact] public void ReadGenericTypeInstantiation() { @@ -33,17 +33,17 @@ public void ReadGenericTypeInstantiation() [Fact] public void PersistentGenericTypeInstantiation() - { + { var module = ModuleDefinition.FromFile(typeof(GenericsTestClass).Assembly.Location); - + using var tempStream = new MemoryStream(); module.Write(tempStream); - + module = ModuleDefinition.FromBytes(tempStream.ToArray()); var fieldType = module - .TopLevelTypes.First(t => t.Name == nameof(GenericsTestClass)) - .Fields.First(f => f.Name == nameof(GenericsTestClass.GenericField)) - .Signature.FieldType; + .TopLevelTypes.First(t => t.Name == nameof(GenericsTestClass))! + .Fields.First(f => f.Name == nameof(GenericsTestClass.GenericField))! + .Signature!.FieldType; Assert.IsAssignableFrom(fieldType); var genericType = (GenericInstanceTypeSignature) fieldType; @@ -53,7 +53,7 @@ public void PersistentGenericTypeInstantiation() "System.String", "System.Int32", "System.Object" }, genericType.TypeArguments.Select(a => a.FullName)); } - + [Fact] public void IllegalTypeSpecInTypeDefOrRef() { @@ -61,7 +61,7 @@ public void IllegalTypeSpecInTypeDefOrRef() var typeSpec = (TypeSpecification) module.LookupMember(new MetadataToken(TableIndex.TypeSpec, 1)); Assert.NotNull(typeSpec); } - + [Fact] public void MaliciousTypeSpecLoop() { @@ -70,6 +70,6 @@ public void MaliciousTypeSpecLoop() var typeSpec = (TypeSpecification) module.LookupMember(new MetadataToken(TableIndex.TypeSpec, 1)); Assert.NotNull(typeSpec.Signature); } - + } -} \ No newline at end of file +} From 91ead067ce2ab3159dc3eeb0adffd14126c8f06d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:38:26 +0000 Subject: [PATCH 122/182] Bump Microsoft.NETFramework.ReferenceAssemblies from 1.0.2 to 1.0.3 Bumps [Microsoft.NETFramework.ReferenceAssemblies](https://github.com/Microsoft/dotnet) from 1.0.2 to 1.0.3. - [Release notes](https://github.com/Microsoft/dotnet/releases) - [Commits](https://github.com/Microsoft/dotnet/commits) --- updated-dependencies: - dependency-name: Microsoft.NETFramework.ReferenceAssemblies dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj | 2 +- test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj index 21604ba0d..329c82d3e 100644 --- a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj +++ b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj @@ -10,6 +10,6 @@ AnyCPU - + diff --git a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj index 264964033..02d35264b 100644 --- a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj +++ b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj @@ -5,7 +5,7 @@ net47;netcoreapp3.1 - + From 4cfa428e5a49fcfdeab09853b4cccb15c5d09315 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 23 Aug 2022 15:58:33 +0200 Subject: [PATCH 123/182] Add PdbStream. --- .../Metadata/DefaultMetadataStreamReader.cs | 9 +- .../DotNet/Metadata/Pdb/PdbStream.cs | 96 +++++++++++++++++++ .../Metadata/Pdb/SerializedPdbStream.cs | 49 ++++++++++ .../DotNet/Metadata/Tables/TablesStream.cs | 2 +- 4 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs index 5927c54b5..93cfd5920 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs @@ -1,7 +1,7 @@ -using System; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Blob; using AsmResolver.PE.DotNet.Metadata.Guid; +using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.UserStrings; @@ -16,7 +16,9 @@ namespace AsmResolver.PE.DotNet.Metadata public class DefaultMetadataStreamReader : IMetadataStreamReader { /// - public IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader header, + public IMetadataStream ReadStream( + PEReaderContext context, + MetadataStreamHeader header, ref BinaryStreamReader reader) { switch (header.Name) @@ -37,6 +39,9 @@ public class DefaultMetadataStreamReader : IMetadataStreamReader case GuidStream.DefaultName: return new SerializedGuidStream(header.Name, reader); + case PdbStream.DefaultName: + return new SerializedPdbStream(header.Name, reader); + default: return new CustomMetadataStream(header.Name, DataSegment.FromReader(ref reader)); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs new file mode 100644 index 000000000..a7a8733dd --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs @@ -0,0 +1,96 @@ +using System; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Tables; + +namespace AsmResolver.PE.DotNet.Metadata.Pdb +{ + /// + /// Represents the metadata stream containing Portable PDB debug data that is associated to a .NET module. + /// + public class PdbStream : SegmentBase, IMetadataStream + { + /// + /// The default name of a PDB stream, as described in the specification provided by Portable PDB v1.0. + /// + public const string DefaultName = "#Pdb"; + + /// + public string Name + { + get; + set; + } = DefaultName; + + /// + public virtual bool CanRead => false; + + /// + /// Gets the unique identifier representing the debugging metadata blob content. + /// + public byte[] Id + { + get; + } = new byte[20]; + + /// + /// Gets or sets the token of the entry point method, or 9 if not applicable. + /// + /// + /// This should be the same value as stored in the metadata header. + /// + public MetadataToken EntryPoint + { + get; + set; + } + + /// + /// Gets an array of row counts of every portable PDB table in the tables stream. + /// + public uint[] TypeSystemTableRows + { + get; + set; + } + + /// + /// Computes the valid bitmask for the type system table rows referenced by this pdb stream. + /// + /// The bitmask. + public ulong ComputeReferencedTypeSystemTables() + { + ulong result = 0; + + for (int i = 0; i < TypeSystemTableRows.Length; i++) + { + if (TypeSystemTableRows[i] != 0) + result |= 1UL << i; + } + + return result; + } + + /// + public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException(); + + /// + public override uint GetPhysicalSize() + { + return 20 // ID + + sizeof(uint) // EntryPoint + + sizeof(ulong) // ReferencedTypeSystemTables + + 4 * (uint) TypeSystemTableRows.Length; // TypeSystemTableRows. + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + writer.WriteBytes(Id); + writer.WriteUInt32(EntryPoint.ToUInt32()); + writer.WriteUInt64(ComputeReferencedTypeSystemTables()); + foreach (uint row in TypeSystemTableRows) + writer.WriteUInt32(row); + } + + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs new file mode 100644 index 000000000..f13f5bbf0 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs @@ -0,0 +1,49 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Pdb +{ + /// + /// Provides an implementation of a PDB stream that obtains GUIDs from a readable segment in a file. + /// + public class SerializedPdbStream : PdbStream + { + /// + /// Creates a new PDB stream with the provided byte array as the raw contents of the stream. + /// + /// The raw contents of the stream. + public SerializedPdbStream(byte[] rawData) + : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + { + } + + /// + /// Creates a new PDB stream with the provided byte array as the raw contents of the stream. + /// + /// The name of the stream. + /// The raw contents of the stream. + public SerializedPdbStream(string name, byte[] rawData) + : this(name, ByteArrayDataSource.CreateReader(rawData)) + { + } + + /// + /// Creates a new PDB stream with the provided file segment reader as the raw contents of the stream. + /// + /// The name of the stream. + /// The raw contents of the stream. + public SerializedPdbStream(string name, in BinaryStreamReader reader) + { + Name = name; + Offset = reader.Offset; + Rva = reader.Rva; + + var headerReader = reader.Fork(); + + headerReader.ReadBytes(Id, 0, Id.Length); + EntryPoint = headerReader.ReadUInt32(); + + ulong mask = headerReader.ReadUInt64(); + + } + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index e60803176..8d4bbbeab 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -11,7 +11,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// public class TablesStream : SegmentBase, IMetadataStream { - private const TableIndex MaxTypeSystemTableIndex = TableIndex.GenericParamConstraint; + protected const TableIndex MaxTypeSystemTableIndex = TableIndex.GenericParamConstraint; /// /// The default name of a table stream using the compressed format. From 0c17393bbefbdefac8eaab8a6da3a0e5d7ba9100 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 23 Aug 2022 16:32:06 +0200 Subject: [PATCH 124/182] Replace SerializedMetadata's PEReaderContext with more conservative MetadataReaderContext. --- src/AsmResolver.PE.File/IPEFile.cs | 2 +- .../Metadata/DefaultMetadataStreamReader.cs | 11 ++- .../DotNet/Metadata/IMetadataStreamReader.cs | 2 +- .../DotNet/Metadata/MetadataReaderContext.cs | 75 +++++++++++++++++++ .../DotNet/Metadata/MetadataStreamList.cs | 6 +- .../DotNet/Metadata/SerializedMetadata.cs | 4 +- .../Metadata/Tables/Rows/FieldRvaRow.cs | 4 +- .../Tables/Rows/MethodDefinitionRow.cs | 4 +- .../Tables/SerializedMetadataTable.cs | 4 +- .../Metadata/Tables/SerializedTableStream.cs | 6 +- .../DotNet/SerializedDotNetDirectory.cs | 3 +- ...esolver.cs => ISegmentReferenceFactory.cs} | 30 ++++---- .../DotNet/Metadata/MetadataTest.cs | 3 +- .../Metadata/Tables/Rows/RowTestUtils.cs | 3 +- .../Metadata/Tables/TablesStreamTest.cs | 4 +- test/AsmResolver.PE.Tests/VirtualAddress.cs | 2 +- .../VirtualAddressFactory.cs | 12 +++ 17 files changed, 136 insertions(+), 39 deletions(-) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs rename src/AsmResolver/{ISegmentReferenceResolver.cs => ISegmentReferenceFactory.cs} (86%) create mode 100644 test/AsmResolver.PE.Tests/VirtualAddressFactory.cs diff --git a/src/AsmResolver.PE.File/IPEFile.cs b/src/AsmResolver.PE.File/IPEFile.cs index 57e4e50a9..abc25a417 100644 --- a/src/AsmResolver.PE.File/IPEFile.cs +++ b/src/AsmResolver.PE.File/IPEFile.cs @@ -9,7 +9,7 @@ namespace AsmResolver.PE.File /// /// Provides a writable implementation of the interface. /// - public interface IPEFile : ISegmentReferenceResolver, IOffsetConverter + public interface IPEFile : ISegmentReferenceFactory, IOffsetConverter { /// /// When this PE file was read from the disk, gets the file path to the PE file. diff --git a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs index 93cfd5920..2c4d3364c 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs @@ -15,9 +15,16 @@ namespace AsmResolver.PE.DotNet.Metadata /// public class DefaultMetadataStreamReader : IMetadataStreamReader { + /// + /// Gets a default instance + /// + public static DefaultMetadataStreamReader Instance + { + get; + } = new(); + /// - public IMetadataStream ReadStream( - PEReaderContext context, + public IMetadataStream ReadStream(MetadataReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader) { diff --git a/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs index 319459f43..01a4ed0c1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs @@ -14,6 +14,6 @@ public interface IMetadataStreamReader /// The header of the metadata stream. /// The input stream to read from. /// The read metadata stream. - IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader); + IMetadataStream ReadStream(MetadataReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader); } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs new file mode 100644 index 000000000..e16c9100b --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs @@ -0,0 +1,75 @@ +using System; + +namespace AsmResolver.PE.DotNet.Metadata +{ + /// + /// Provides a context for a .NET metadata directory reader. + /// + public class MetadataReaderContext : IErrorListener + { + /// + /// Constructs a new metadata reader context. + /// + /// The factory object responsible for translating RVAs to references. + public MetadataReaderContext(ISegmentReferenceFactory factory) + : this(factory, ThrowErrorListener.Instance, DefaultMetadataStreamReader.Instance) + { + } + + /// + /// Constructs a new metadata reader context. + /// + /// The factory object responsible for translating RVAs to references. + /// The object responsible for collecting any errors during the parsing. + /// The object responsible for reading metadata streams in the .NET data directory. + public MetadataReaderContext( + ISegmentReferenceFactory referenceFactory, + IErrorListener errorListener, + IMetadataStreamReader metadataStreamReader) + { + ReferenceFactory = referenceFactory; + ErrorListener = errorListener; + MetadataStreamReader = metadataStreamReader; + } + + /// + /// Gets the factory responsible for translating RVAs to references. + /// + public ISegmentReferenceFactory ReferenceFactory + { + get; + } + + /// + /// Gets the object responsible for collecting any errors during the parsing. + /// + public IErrorListener ErrorListener + { + get; + } + + /// + /// Gets the object responsible for reading metadata streams in the .NET data directory. + /// + public IMetadataStreamReader MetadataStreamReader + { + get; + } + + /// + /// Constructs a metadata reader context from a PE reader context. + /// + /// The context to transform. + /// The constructed context. + public static MetadataReaderContext FromReaderContext(PEReaderContext context) => new( + context.File, + context.Parameters.ErrorListener, + context.Parameters.MetadataStreamReader); + + /// + public void MarkAsFatal() => ErrorListener.MarkAsFatal(); + + /// + public void RegisterException(Exception exception) => ErrorListener.RegisterException(exception); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs index aaad91be3..504a3b59b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs @@ -9,7 +9,7 @@ namespace AsmResolver.PE.DotNet.Metadata /// public class MetadataStreamList : LazyList { - private readonly PEReaderContext _context; + private readonly MetadataReaderContext _context; private readonly int _numberOfStreams; private BinaryStreamReader _directoryReader; private BinaryStreamReader _entriesReader; @@ -22,7 +22,7 @@ public class MetadataStreamList : LazyList /// The input stream containing the metadata stream entries. /// The number of streams. public MetadataStreamList( - PEReaderContext context, + MetadataReaderContext context, in BinaryStreamReader directoryReader, in BinaryStreamReader entriesReader, int numberOfStreams) @@ -48,7 +48,7 @@ protected override void Initialize() var header = headers[i]; var streamReader = _directoryReader.ForkAbsolute(_directoryReader.Offset + header.Offset, headers[i].Size); - var stream = _context.Parameters.MetadataStreamReader.ReadStream(_context, header, ref streamReader); + var stream = _context.MetadataStreamReader.ReadStream(_context, header, ref streamReader); Items.Add(stream); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs index 1c3ab7bb2..52eb606fc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs @@ -10,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata /// public class SerializedMetadata : Metadata { - private readonly PEReaderContext _context; + private readonly MetadataReaderContext _context; private readonly BinaryStreamReader _streamEntriesReader; private readonly BinaryStreamReader _streamContentsReader; private readonly int _numberOfStreams; @@ -23,7 +23,7 @@ public class SerializedMetadata : Metadata /// Occurs when any of the arguments are null. /// Occurs when an unsupported metadata directory format was encountered. /// Occurs when the metadata directory header is invalid. - public SerializedMetadata(PEReaderContext context, ref BinaryStreamReader directoryReader) + public SerializedMetadata(MetadataReaderContext context, ref BinaryStreamReader directoryReader) { if (!directoryReader.IsValid) throw new ArgumentNullException(nameof(directoryReader)); diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs index 97d7b6b92..d99f13746 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs @@ -17,10 +17,10 @@ public struct FieldRvaRow : IMetadataRow /// The input stream. /// The layout of the field RVA table. /// The row. - public static FieldRvaRow FromReader(PEReaderContext context, ref BinaryStreamReader reader, TableLayout layout) + public static FieldRvaRow FromReader(MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout) { return new FieldRvaRow( - context.File.GetReferenceToRva(reader.ReadUInt32()), + context.ReferenceFactory.GetReferenceToRva(reader.ReadUInt32()), reader.ReadIndex((IndexSize) layout.Columns[1].Size)); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs index 2b6df546f..6cd6e5481 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs @@ -17,10 +17,10 @@ public struct MethodDefinitionRow : IMetadataRow /// The input stream. /// The layout of the method definition table. /// The row. - public static MethodDefinitionRow FromReader(PEReaderContext context, ref BinaryStreamReader reader, TableLayout layout) + public static MethodDefinitionRow FromReader(MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout) { return new MethodDefinitionRow( - context.File.GetReferenceToRva(reader.ReadUInt32()), + context.ReferenceFactory.GetReferenceToRva(reader.ReadUInt32()), (MethodImplAttributes) reader.ReadUInt16(), (MethodAttributes) reader.ReadUInt16(), reader.ReadIndex((IndexSize) layout.Columns[3].Size), diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs index 0529b40f3..33255673b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs @@ -27,7 +27,7 @@ public class SerializedMetadataTable : MetadataTable /// The input stream. /// The layout of the table. public delegate TRow ReadRowExtendedDelegate( - PEReaderContext context, + MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout); @@ -61,7 +61,7 @@ public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableInd /// The layout of the table. /// The method to use for reading each row in the table. public SerializedMetadataTable( - PEReaderContext context, + MetadataReaderContext context, in BinaryStreamReader reader, TableIndex tableIndex, TableLayout originalLayout, diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs index 04ba90cd4..b6aaaa29d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs @@ -11,7 +11,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// public class SerializedTableStream : TablesStream { - private readonly PEReaderContext _context; + private readonly MetadataReaderContext _context; private readonly BinaryStreamReader _reader; private readonly ulong _validMask; private readonly ulong _sortedMask; @@ -26,7 +26,7 @@ public class SerializedTableStream : TablesStream /// The reader context. /// The name of the stream. /// The raw contents of the stream. - public SerializedTableStream(PEReaderContext context, string name, byte[] rawData) + public SerializedTableStream(MetadataReaderContext context, string name, byte[] rawData) : this(context, name, ByteArrayDataSource.CreateReader(rawData)) { } @@ -37,7 +37,7 @@ public SerializedTableStream(PEReaderContext context, string name, byte[] rawDat /// The reader context. /// The name of the stream. /// The raw contents of the stream. - public SerializedTableStream(PEReaderContext context, string name, in BinaryStreamReader reader) + public SerializedTableStream(MetadataReaderContext context, string name, in BinaryStreamReader reader) { Name = name ?? throw new ArgumentNullException(nameof(name)); _context = context ?? throw new ArgumentNullException(nameof(context)); diff --git a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs index c75dd2a25..5c8fbaa2f 100644 --- a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs @@ -60,8 +60,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader return null; } - return new SerializedMetadata(_context, ref directoryReader); - + return new SerializedMetadata(MetadataReaderContext.FromReaderContext(_context), ref directoryReader); } /// diff --git a/src/AsmResolver/ISegmentReferenceResolver.cs b/src/AsmResolver/ISegmentReferenceFactory.cs similarity index 86% rename from src/AsmResolver/ISegmentReferenceResolver.cs rename to src/AsmResolver/ISegmentReferenceFactory.cs index 7f9db42e9..7fff84851 100644 --- a/src/AsmResolver/ISegmentReferenceResolver.cs +++ b/src/AsmResolver/ISegmentReferenceFactory.cs @@ -1,15 +1,15 @@ -namespace AsmResolver -{ - /// - /// Provides members for resolving virtual addresses to a segment in a binary file. - /// - public interface ISegmentReferenceResolver - { - /// - /// Resolves the provided virtual address to a segment reference. - /// - /// The virtual address of the segment. - /// The reference to the segment. - ISegmentReference GetReferenceToRva(uint rva); - } -} \ No newline at end of file +namespace AsmResolver +{ + /// + /// Provides members for resolving virtual addresses to a segment in a binary file. + /// + public interface ISegmentReferenceFactory + { + /// + /// Resolves the provided virtual address to a segment reference. + /// + /// The virtual address of the segment. + /// The reference to the segment. + ISegmentReference GetReferenceToRva(uint rva); + } +} diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs index ed16611f3..14441d2a0 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs @@ -104,7 +104,8 @@ public void PreserveMetadataNoChange() metadata.Write(new BinaryStreamWriter(tempStream)); var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); - var newMetadata = new SerializedMetadata(new PEReaderContext(peFile), ref reader); + var context = MetadataReaderContext.FromReaderContext(new PEReaderContext(peFile)); + var newMetadata = new SerializedMetadata(context, ref reader); Assert.Equal(metadata.MajorVersion, newMetadata.MajorVersion); Assert.Equal(metadata.MinorVersion, newMetadata.MinorVersion); diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs index 7566e52bd..8e4bcb459 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using AsmResolver.PE.File; @@ -36,7 +37,7 @@ internal static class RowTestUtils using var tempStream = new MemoryStream(); expected.Write(new BinaryStreamWriter(tempStream), table.Layout); var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); - var newRow = readRow(new PEReaderContext(new PEFile()), ref reader, table.Layout); + var newRow = readRow(new MetadataReaderContext(VirtualAddressFactory.Instance), ref reader, table.Layout); Assert.Equal(expected, newRow); } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs index d35b8fd43..134cf6375 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs @@ -1,5 +1,6 @@ using System.IO; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.File; using Xunit; @@ -37,7 +38,8 @@ public void PreserveTableStreamNoChange() using var tempStream = new MemoryStream(); tablesStream.Write(new BinaryStreamWriter(tempStream)); - var newTablesStream = new SerializedTableStream(new PEReaderContext(new PEFile()), tablesStream.Name, tempStream.ToArray()); + var context = new MetadataReaderContext(VirtualAddressFactory.Instance); + var newTablesStream = new SerializedTableStream(context, tablesStream.Name, tempStream.ToArray()); Assert.Equal(tablesStream.Reserved, newTablesStream.Reserved); Assert.Equal(tablesStream.MajorVersion, newTablesStream.MajorVersion); diff --git a/test/AsmResolver.PE.Tests/VirtualAddress.cs b/test/AsmResolver.PE.Tests/VirtualAddress.cs index b9424e496..daf947fed 100644 --- a/test/AsmResolver.PE.Tests/VirtualAddress.cs +++ b/test/AsmResolver.PE.Tests/VirtualAddress.cs @@ -3,7 +3,7 @@ namespace AsmResolver.PE.Tests { - public readonly struct VirtualAddress : ISegmentReference + public sealed class VirtualAddress : ISegmentReference { public VirtualAddress(uint rva) { diff --git a/test/AsmResolver.PE.Tests/VirtualAddressFactory.cs b/test/AsmResolver.PE.Tests/VirtualAddressFactory.cs new file mode 100644 index 000000000..91eac00d4 --- /dev/null +++ b/test/AsmResolver.PE.Tests/VirtualAddressFactory.cs @@ -0,0 +1,12 @@ +namespace AsmResolver.PE.Tests +{ + public class VirtualAddressFactory : ISegmentReferenceFactory + { + public static VirtualAddressFactory Instance + { + get; + } = new(); + + public ISegmentReference GetReferenceToRva(uint rva) => new VirtualAddress(rva); + } +} From e493785ffde3d14b9bd859d47756076e0343ee94 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 23 Aug 2022 16:45:07 +0200 Subject: [PATCH 125/182] Move VirtualAddress and factory into main AsmResolver library. --- .../DotNet/Metadata/Metadata.cs | 42 +++++++++++++++++++ .../DotNet/SerializedDotNetDirectory.cs | 2 +- .../AsmResolver}/VirtualAddress.cs | 13 +++++- src/AsmResolver/VirtualAddressFactory.cs | 19 +++++++++ .../VirtualAddressFactory.cs | 12 ------ 5 files changed, 73 insertions(+), 15 deletions(-) rename {test/AsmResolver.PE.Tests => src/AsmResolver}/VirtualAddress.cs (53%) create mode 100644 src/AsmResolver/VirtualAddressFactory.cs delete mode 100644 test/AsmResolver.PE.Tests/VirtualAddressFactory.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs index cc1b88b5c..9bc2e415a 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs @@ -60,6 +60,48 @@ public IList Streams } } + /// + /// Reads a .NET metadata directory from a file. + /// + /// The path to the file. + /// The read metadata. + public static Metadata FromFile(string path) => FromBytes(System.IO.File.ReadAllBytes(path)); + + /// + /// Interprets the provided binary data as a .NET metadata directory. + /// + /// The raw data. + /// The read metadata. + public static Metadata FromBytes(byte[] data) => FromReader(ByteArrayDataSource.CreateReader(data)); + + /// + /// Reads a .NET metadata directory from a file. + /// + /// The file to read. + /// The read metadata. + public static Metadata FromFile(IInputFile file) => FromReader(file.CreateReader()); + + /// + /// Interprets the provided binary stream as a .NET metadata directory. + /// + /// The input stream. + /// The read metadata. + public static Metadata FromReader(BinaryStreamReader reader) + { + return FromReader(reader, new MetadataReaderContext(VirtualAddressFactory.Instance)); + } + + /// + /// Interprets the provided binary stream as a .NET metadata directory. + /// + /// The input stream. + /// The context in which the reader is situated in. + /// The read metadata. + public static Metadata FromReader(BinaryStreamReader reader, MetadataReaderContext context) + { + return new SerializedMetadata(context, ref reader); + } + /// public override uint GetPhysicalSize() { diff --git a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs index 85e507f9d..6ab56fa40 100644 --- a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs @@ -60,7 +60,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader return null; } - return new SerializedMetadata(MetadataReaderContext.FromReaderContext(_context), ref directoryReader); + return DotNet.Metadata.Metadata.FromReader(directoryReader, MetadataReaderContext.FromReaderContext(_context)); } /// diff --git a/test/AsmResolver.PE.Tests/VirtualAddress.cs b/src/AsmResolver/VirtualAddress.cs similarity index 53% rename from test/AsmResolver.PE.Tests/VirtualAddress.cs rename to src/AsmResolver/VirtualAddress.cs index 143ebbbbf..7975ba7a6 100644 --- a/test/AsmResolver.PE.Tests/VirtualAddress.cs +++ b/src/AsmResolver/VirtualAddress.cs @@ -1,10 +1,17 @@ using System; using AsmResolver.IO; -namespace AsmResolver.PE.Tests +namespace AsmResolver { + /// + /// Represents a (relative) virtual address in a file. + /// public sealed class VirtualAddress : ISegmentReference { + /// + /// Wraps a relative virtual address into a object. + /// + /// public VirtualAddress(uint rva) { Rva = rva; @@ -12,17 +19,19 @@ public VirtualAddress(uint rva) ulong IOffsetProvider.Offset => Rva; + /// public uint Rva { get; } + /// public bool CanRead => false; bool ISegmentReference.IsBounded => false; BinaryStreamReader ISegmentReference.CreateReader() => throw new InvalidOperationException(); - public ISegment? GetSegment() => throw new InvalidOperationException(); + ISegment? ISegmentReference.GetSegment() => throw new InvalidOperationException(); } } diff --git a/src/AsmResolver/VirtualAddressFactory.cs b/src/AsmResolver/VirtualAddressFactory.cs new file mode 100644 index 000000000..b5af70799 --- /dev/null +++ b/src/AsmResolver/VirtualAddressFactory.cs @@ -0,0 +1,19 @@ +namespace AsmResolver +{ + /// + /// Provides an implementation of a reference factory that constructs objects. + /// + public class VirtualAddressFactory : ISegmentReferenceFactory + { + /// + /// Gets the default instance of this factory. + /// + public static VirtualAddressFactory Instance + { + get; + } = new(); + + /// + public ISegmentReference GetReferenceToRva(uint rva) => new VirtualAddress(rva); + } +} diff --git a/test/AsmResolver.PE.Tests/VirtualAddressFactory.cs b/test/AsmResolver.PE.Tests/VirtualAddressFactory.cs deleted file mode 100644 index 91eac00d4..000000000 --- a/test/AsmResolver.PE.Tests/VirtualAddressFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace AsmResolver.PE.Tests -{ - public class VirtualAddressFactory : ISegmentReferenceFactory - { - public static VirtualAddressFactory Instance - { - get; - } = new(); - - public ISegmentReference GetReferenceToRva(uint rva) => new VirtualAddress(rva); - } -} From 28f812d67f6e25bb84a2e3549421f588fa2dedc8 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 23 Aug 2022 17:09:49 +0200 Subject: [PATCH 126/182] Add PdbStream::UpdateRowCounts --- .../DotNet/Metadata/Pdb/PdbStream.cs | 15 ++++++++++++++- .../DotNet/Metadata/Pdb/SerializedPdbStream.cs | 16 +++++++++++++++- .../DotNet/Metadata/PdbStreamTest.cs | 17 +++++++++++++++++ .../Properties/Resources.Designer.cs | 7 +++++++ .../Properties/Resources.resx | 3 +++ 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs index a7a8733dd..7d5f44a1a 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs @@ -50,7 +50,20 @@ public MetadataToken EntryPoint public uint[] TypeSystemTableRows { get; - set; + } = new uint[(int) TableIndex.Max]; + + /// + /// Synchronizes the row counts stored in with the tables in the provided + /// tables stream. + /// + /// The tables stream to pull the data from. + public void UpdateRowCounts(TablesStream stream) + { + for (TableIndex i = 0; i < TableIndex.Max; i++) + { + if (i.IsValidTableIndex()) + TypeSystemTableRows[(int) i] = (uint) stream.GetTable(i).Count; + } } /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs index f13f5bbf0..7d4bf217a 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs @@ -7,6 +7,8 @@ namespace AsmResolver.PE.DotNet.Metadata.Pdb /// public class SerializedPdbStream : PdbStream { + private readonly BinaryStreamReader _reader; + /// /// Creates a new PDB stream with the provided byte array as the raw contents of the stream. /// @@ -33,6 +35,8 @@ public SerializedPdbStream(string name, byte[] rawData) /// The raw contents of the stream. public SerializedPdbStream(string name, in BinaryStreamReader reader) { + _reader = reader; + Name = name; Offset = reader.Offset; Rva = reader.Rva; @@ -43,7 +47,17 @@ public SerializedPdbStream(string name, in BinaryStreamReader reader) EntryPoint = headerReader.ReadUInt32(); ulong mask = headerReader.ReadUInt64(); - + for (int i = 0; i < 64; i++) + { + if (((mask >> i) & 1) != 0) + TypeSystemTableRows[i] = headerReader.ReadUInt32(); + } } + + /// + public override bool CanRead => true; + + /// + public override BinaryStreamReader CreateReader() => _reader.Fork(); } } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs new file mode 100644 index 000000000..5a8971e4b --- /dev/null +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs @@ -0,0 +1,17 @@ +using AsmResolver.PE.DotNet.Metadata.Pdb; +using AsmResolver.PE.DotNet.Metadata.Tables; +using Xunit; + +namespace AsmResolver.PE.Tests.DotNet.Metadata +{ + public class PdbStreamTest + { + [Fact] + public void PdbStream() + { + var metadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.HelloWorldPortablePdb); + var pdbStream = metadata.GetStream(); + var tablesStream = metadata.GetStream(); + } + } +} diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 388c1419d..81aa59c61 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -94,6 +94,13 @@ public class Resources { } } + public static byte[] HelloWorldPortablePdb { + get { + object obj = ResourceManager.GetObject("HelloWorldPortablePdb", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] SimpleDll { get { object obj = ResourceManager.GetObject("SimpleDll", resourceCulture); diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index e9a93ecd7..54702ef16 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -39,6 +39,9 @@ ..\Resources\HelloWorld.TablesStream.ExtraData.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\SimpleDll.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 From 6cfb095003fef3fd783310dcaad854187f130422 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 24 Aug 2022 21:16:25 +0200 Subject: [PATCH 127/182] Add TablesStream.ExternalRowCounts, ILazyMetadatStream. --- .../DotNet/Metadata/ILazyMetadataStream.cs | 14 +++ .../DotNet/Metadata/MetadataStreamList.cs | 15 ++- .../DotNet/Metadata/Pdb/PdbStream.cs | 37 +++++-- .../Metadata/Pdb/SerializedPdbStream.cs | 2 +- .../DotNet/Metadata/SerializedMetadata.cs | 5 +- .../DotNet/Metadata/Tables/IndexEncoder.cs | 20 ++-- .../Metadata/Tables/SerializedTableStream.cs | 64 +++++++++++-- .../DotNet/Metadata/Tables/TableIndex.cs | 2 + .../DotNet/Metadata/Tables/TablesStream.cs | 96 ++++++++++++++----- src/AsmResolver/Collections/LazyList.cs | 14 +++ .../DotNet/Metadata/PdbStreamTest.cs | 49 +++++++++- .../Metadata/Tables/TablesStreamTest.cs | 78 +++++++++++++-- .../Properties/Resources.Designer.cs | 14 +++ .../Properties/Resources.resx | 6 ++ 14 files changed, 348 insertions(+), 68 deletions(-) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs b/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs new file mode 100644 index 000000000..0877d6e7d --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs @@ -0,0 +1,14 @@ +namespace AsmResolver.PE.DotNet.Metadata +{ + /// + /// Represents a metadata stream that is initialized lazily. + /// + public interface ILazyMetadataStream : IMetadataStream + { + /// + /// Finalizes the initialization process of the metadata stream. + /// + /// The metadata directory that defines the stream. + void Initialize(IMetadata parentMetadata); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs index 504a3b59b..0fe56918b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs @@ -10,24 +10,28 @@ namespace AsmResolver.PE.DotNet.Metadata public class MetadataStreamList : LazyList { private readonly MetadataReaderContext _context; + private readonly IMetadata _owner; private readonly int _numberOfStreams; - private BinaryStreamReader _directoryReader; + private readonly BinaryStreamReader _directoryReader; private BinaryStreamReader _entriesReader; /// /// Prepares a new lazy-initialized metadata stream list. /// + /// The owner of the metadata stream list. /// The reader context. /// The input stream containing the metadata directory. /// The input stream containing the metadata stream entries. /// The number of streams. public MetadataStreamList( + IMetadata owner, MetadataReaderContext context, in BinaryStreamReader directoryReader, in BinaryStreamReader entriesReader, int numberOfStreams) { _context = context ?? throw new ArgumentNullException(nameof(context)); + _owner = owner; _directoryReader = directoryReader; _entriesReader = entriesReader; _numberOfStreams = numberOfStreams; @@ -54,5 +58,14 @@ protected override void Initialize() } } + /// + protected override void PostInitialize() + { + for (int i = 0; i < _numberOfStreams; i++) + { + if (Items[i] is ILazyMetadataStream lazyStream) + lazyStream.Initialize(_owner); + } + } } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs index 7d5f44a1a..4c4d7e002 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Tables; @@ -47,22 +48,36 @@ public MetadataToken EntryPoint /// /// Gets an array of row counts of every portable PDB table in the tables stream. /// - public uint[] TypeSystemTableRows + public uint[] TypeSystemRowCounts { get; } = new uint[(int) TableIndex.Max]; /// - /// Synchronizes the row counts stored in with the tables in the provided + /// Synchronizes the row counts stored in with the tables in the provided /// tables stream. /// /// The tables stream to pull the data from. public void UpdateRowCounts(TablesStream stream) { - for (TableIndex i = 0; i < TableIndex.Max; i++) + for (TableIndex i = 0; i < TableIndex.MaxTypeSystemTableIndex; i++) { if (i.IsValidTableIndex()) - TypeSystemTableRows[(int) i] = (uint) stream.GetTable(i).Count; + TypeSystemRowCounts[(int) i] = (uint) stream.GetTable(i).Count; + } + } + + /// + /// Synchronizes the row counts stored in with the tables in the provided + /// tables stream row counts. + /// + /// The tables stream row counts to pull in. + public void UpdateRowCounts(uint[] rowCounts) + { + for (TableIndex i = 0; i < TableIndex.MaxTypeSystemTableIndex && (int) i < rowCounts.Length; i++) + { + if (i.IsValidTableIndex()) + TypeSystemRowCounts[(int) i] = rowCounts[(int) i]; } } @@ -74,9 +89,9 @@ public ulong ComputeReferencedTypeSystemTables() { ulong result = 0; - for (int i = 0; i < TypeSystemTableRows.Length; i++) + for (int i = 0; i < TypeSystemRowCounts.Length; i++) { - if (TypeSystemTableRows[i] != 0) + if (TypeSystemRowCounts[i] != 0) result |= 1UL << i; } @@ -92,7 +107,7 @@ public override uint GetPhysicalSize() return 20 // ID + sizeof(uint) // EntryPoint + sizeof(ulong) // ReferencedTypeSystemTables - + 4 * (uint) TypeSystemTableRows.Length; // TypeSystemTableRows. + + 4 * (uint) TypeSystemRowCounts.Count(c => c != 0); // TypeSystemTableRows. } /// @@ -101,8 +116,12 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteBytes(Id); writer.WriteUInt32(EntryPoint.ToUInt32()); writer.WriteUInt64(ComputeReferencedTypeSystemTables()); - foreach (uint row in TypeSystemTableRows) - writer.WriteUInt32(row); + + foreach (uint count in TypeSystemRowCounts) + { + if (count != 0) + writer.WriteUInt32(count); + } } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs index 7d4bf217a..d6603f0b8 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs @@ -50,7 +50,7 @@ public SerializedPdbStream(string name, in BinaryStreamReader reader) for (int i = 0; i < 64; i++) { if (((mask >> i) & 1) != 0) - TypeSystemTableRows[i] = headerReader.ReadUInt32(); + TypeSystemRowCounts[i] = headerReader.ReadUInt32(); } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs index 52eb606fc..74cecd3da 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs @@ -75,11 +75,10 @@ protected override IList GetStreams() if (_numberOfStreams == 0) return base.GetStreams(); - return new MetadataStreamList( + return new MetadataStreamList(this, _context, _streamContentsReader, - _streamEntriesReader, - _numberOfStreams); + _streamEntriesReader, _numberOfStreams); } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs index 690ba9dff..de4f81473 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs @@ -27,7 +27,7 @@ public IndexEncoder(TablesStream tableStream, params TableIndex[] tables) _tableIndexBitCount = (int)Math.Ceiling(Math.Log(tables.Length, 2)); _tableIndexBitMask = (int)(Math.Pow(2, _tableIndexBitCount) - 1); _maxSmallTableMemberCount = ushort.MaxValue >> _tableIndexBitCount; - + } /// @@ -37,11 +37,13 @@ public IndexSize IndexSize { get { - int maxCount = _tables - .Select(table => _tableStream.GetTable(table).Count) - .Max(); + uint maxCount = 0; + foreach (var table in _tables) + maxCount = Math.Max(maxCount, _tableStream.GetTableRowCount(table)); - return maxCount > _maxSmallTableMemberCount ? IndexSize.Long : IndexSize.Short; + return maxCount > _maxSmallTableMemberCount + ? IndexSize.Long + : IndexSize.Short; } } @@ -71,11 +73,11 @@ public MetadataToken DecodeIndex(uint codedIndex) { long tableIndex = codedIndex & _tableIndexBitMask; uint rowIndex = codedIndex >> _tableIndexBitCount; - - return new MetadataToken(tableIndex >= _tables.Length - ? TableIndex.Module + + return new MetadataToken(tableIndex >= _tables.Length + ? TableIndex.Module : _tables[tableIndex], rowIndex); } } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs index b6aaaa29d..fcddfad97 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.PE.DotNet.Metadata.Tables @@ -9,17 +10,27 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Provides an implementation of a tables stream that obtains tables from a readable segment in a file. /// - public class SerializedTableStream : TablesStream + public class SerializedTableStream : TablesStream, ILazyMetadataStream { private readonly MetadataReaderContext _context; private readonly BinaryStreamReader _reader; private readonly ulong _validMask; private readonly ulong _sortedMask; private readonly uint[] _rowCounts; - private readonly IndexSize[] _indexSizes; private readonly uint _headerSize; private bool _tablesInitialized; + /// + /// Same as but may contain row counts from an external tables stream. + /// This is required for metadata directories containing Portable PDB debug data. + /// + private uint[]? _combinedRowCounts; + + /// + /// Contains the initial sizes of every column type. + /// + private IndexSize[]? _indexSizes; + /// /// Creates a new tables stream with the provided byte array as the raw contents of the stream. /// @@ -54,15 +65,12 @@ public SerializedTableStream(MetadataReaderContext context, string name, in Bina Log2LargestRid = headerReader.ReadByte(); _validMask = headerReader.ReadUInt64(); _sortedMask = headerReader.ReadUInt64(); - _rowCounts = ReadRowCounts(ref headerReader); if (HasExtraData) ExtraData = headerReader.ReadUInt32(); _headerSize = headerReader.RelativeOffset; - - _indexSizes = InitializeIndexSizes(); } /// @@ -75,7 +83,7 @@ private uint[] ReadRowCounts(ref BinaryStreamReader reader) { uint[] result = new uint[(int) TableIndex.Max]; - for (TableIndex i = 0; i <= TableIndex.GenericParamConstraint; i++) + for (TableIndex i = 0; i < TableIndex.Max; i++) { result[(int) i] = HasTable(_validMask, i) ? reader.ReadUInt32() @@ -85,10 +93,43 @@ private uint[] ReadRowCounts(ref BinaryStreamReader reader) return result; } + /// + public void Initialize(IMetadata parentMetadata) + { + if (parentMetadata.TryGetStream(out PdbStream? pdbStream)) + { + // Metadata that contains a PDB stream should use the row counts provided in the pdb stream + // for computing the size of a column. + _combinedRowCounts = new uint[_rowCounts.Length]; + ExternalRowCounts = new uint[(int) TableIndex.Document]; + + for (int i = 0; i < (int) TableIndex.Document; i++) + { + _combinedRowCounts[i] = pdbStream.TypeSystemRowCounts[i]; + ExternalRowCounts[i] = pdbStream.TypeSystemRowCounts[i]; + } + + for (int i = (int) TableIndex.Document; i < (int) TableIndex.Max; i++) + _combinedRowCounts[i] = _rowCounts[i]; + + } + else + { + // Otherwise, just use the original row counts array. + _combinedRowCounts = _rowCounts; + } + + _indexSizes = InitializeIndexSizes(); + } + /// protected override uint GetColumnSize(ColumnType columnType) { - if (_tablesInitialized || (int) columnType >= _indexSizes.Length) + if (_tablesInitialized) + return base.GetColumnSize(columnType); + if (_indexSizes is null) + throw new InvalidOperationException("Serialized tables stream is not fully initialized yet."); + if ((int) columnType >= _indexSizes.Length) return base.GetColumnSize(columnType); return (uint) _indexSizes[(int) columnType]; } @@ -156,8 +197,8 @@ private IndexSize[] InitializeIndexSizes() // HasCustomDebugInformation GetCodedIndexSize(TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, - TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig - , TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, + TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, + TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) @@ -168,10 +209,13 @@ private IndexSize[] InitializeIndexSizes() private IndexSize GetCodedIndexSize(params TableIndex[] tables) { + if (_combinedRowCounts is null) + throw new InvalidOperationException("Serialized tables stream is not fully initialized yet."); + int tableIndexBitCount = (int) Math.Ceiling(Math.Log(tables.Length, 2)); int maxSmallTableMemberCount = ushort.MaxValue >> tableIndexBitCount; - return tables.Select(t => _rowCounts[(int) t]).All(c => c < maxSmallTableMemberCount) + return tables.Select(t => _combinedRowCounts[(int) t]).All(c => c < maxSmallTableMemberCount) ? IndexSize.Short : IndexSize.Long; } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs index 174b67e24..8ee7dd66b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs @@ -54,6 +54,8 @@ public enum TableIndex : byte MethodSpec = 43, GenericParamConstraint = 44, + MaxTypeSystemTableIndex = GenericParamConstraint, + Document = 0x30, MethodDebugInformation = 0x31, LocalScope = 0x32, diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index 8d4bbbeab..bd6c6a73e 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -11,8 +12,6 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// public class TablesStream : SegmentBase, IMetadataStream { - protected const TableIndex MaxTypeSystemTableIndex = TableIndex.GenericParamConstraint; - /// /// The default name of a table stream using the compressed format. /// @@ -194,6 +193,29 @@ public uint ExtraData set; } + /// + /// Gets a value indicating whether the tables stream is assigned with row counts that originate from an + /// external .NET metadata file. + /// + /// + /// This value is typically set to false, except for Portable PDB metadata table streams. + /// + [MemberNotNullWhen(true, nameof(ExternalRowCounts))] + public bool HasExternalRowCounts => ExternalRowCounts is not null; + + /// + /// Gets or sets an array of row counts originating from an external .NET metadata file that this table stream + /// should consider when encoding indices. + /// + /// + /// This value is typically null, except for Portable PDB metadata table streams. + /// + public uint[]? ExternalRowCounts + { + get; + set; + } + /// /// Gets a collection of all tables in the tables stream. /// @@ -211,6 +233,36 @@ public uint ExtraData /// public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException(); + /// + /// Obtains the implied table row count for the provided table index. + /// + /// The table index. + /// The row count. + /// + /// This method takes any external row counts from into account. + /// + public uint GetTableRowCount(TableIndex table) + { + return HasExternalRowCounts && (int) table < ExternalRowCounts.Length + ? ExternalRowCounts[(int) table] + : (uint) GetTable(table).Count; + } + + /// + /// Obtains the implied table index size for the provided table index. + /// + /// The table index. + /// The index size. + /// + /// This method takes any external row counts from into account. + /// + public IndexSize GetTableIndexSize(TableIndex table) + { + return GetTableRowCount(table) > 0xFFFF + ? IndexSize.Long + : IndexSize.Short; + } + /// /// Updates the layouts of each metadata table, according to the property. /// @@ -303,7 +355,7 @@ protected virtual ulong ComputeSortedBitmask() protected virtual int GetTablesCount(ulong validBitmask) { int count = 0; - for (TableIndex i = 0; i <= TableIndex.GenericParamConstraint; i++) + for (TableIndex i = 0; i < TableIndex.Max; i++) { if (HasTable(validBitmask, i)) count++; @@ -339,7 +391,7 @@ protected virtual uint GetTablesSize(ulong validBitmask) /// The valid bitmask, indicating all present tables in the stream. protected virtual void WriteRowCounts(IBinaryStreamWriter writer, ulong validBitmask) { - for (TableIndex i = 0; i <= MaxTypeSystemTableIndex; i++) + for (TableIndex i = 0; i <= TableIndex.Max; i++) { if (HasTable(validBitmask, i)) writer.WriteInt32(GetTable(i).Count); @@ -548,33 +600,25 @@ protected virtual uint GetColumnSize(ColumnType columnType) { if (_layouts.IsInitialized) { - if (columnType <= ColumnType.CustomDebugInformation) + switch (columnType) { - return (uint) (Tables[(int) columnType]?.IndexSize - ?? throw new ArgumentOutOfRangeException(nameof(columnType))); + case <= ColumnType.CustomDebugInformation: + return (uint) GetTableIndexSize((TableIndex) columnType); + case <= ColumnType.HasCustomDebugInformation: + return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize; } - - if (columnType <= ColumnType.HasCustomDebugInformation) - return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize; } - switch (columnType) + return columnType switch { - case ColumnType.Blob: - return (uint) BlobIndexSize; - case ColumnType.String: - return (uint) StringIndexSize; - case ColumnType.Guid: - return (uint) GuidIndexSize; - case ColumnType.Byte: - return sizeof(byte); - case ColumnType.UInt16: - return sizeof(ushort); - case ColumnType.UInt32: - return sizeof(uint); - default: - return sizeof(uint); - } + ColumnType.Blob => (uint) BlobIndexSize, + ColumnType.String => (uint) StringIndexSize, + ColumnType.Guid => (uint) GuidIndexSize, + ColumnType.Byte => sizeof(byte), + ColumnType.UInt16 => sizeof(ushort), + ColumnType.UInt32 => sizeof(uint), + _ => sizeof(uint) + }; } /// diff --git a/src/AsmResolver/Collections/LazyList.cs b/src/AsmResolver/Collections/LazyList.cs index 0dd0cc0ce..139761acf 100644 --- a/src/AsmResolver/Collections/LazyList.cs +++ b/src/AsmResolver/Collections/LazyList.cs @@ -87,6 +87,19 @@ protected bool IsInitialized /// protected abstract void Initialize(); + /// + /// Performs any final adjustments to the collection after all initial items were added to the underlying list. + /// + /// + /// Upon calling this method, the has already been set to true, but the + /// initialization lock has not been released yet. This means that any element in the list is guaranteed + /// to be still in its initial state. It is therefore safe to access elements, as well as adding or removing + /// items from . + /// + protected virtual void PostInitialize() + { + } + private void EnsureIsInitialized() { if (!IsInitialized) @@ -97,6 +110,7 @@ private void EnsureIsInitialized() { Initialize(); IsInitialized = true; + PostInitialize(); } } } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs index 5a8971e4b..6927f3554 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs @@ -1,3 +1,7 @@ +using System.IO; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata; using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Tables; using Xunit; @@ -6,12 +10,51 @@ namespace AsmResolver.PE.Tests.DotNet.Metadata { public class PdbStreamTest { - [Fact] - public void PdbStream() + private static IMetadata GetMetadata(bool rebuild) { - var metadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.HelloWorldPortablePdb); + var metadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + if (rebuild) + { + using var stream = new MemoryStream(); + metadata.Write(new BinaryStreamWriter(stream)); + metadata = PE.DotNet.Metadata.Metadata.FromBytes(stream.ToArray()); + } + + return metadata; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Id(bool rebuild) + { + var metadata = GetMetadata(rebuild); + Assert.Equal(new byte[] + { + 0x95, 0x26, 0xB5, 0xAC, 0xA7, 0xB, 0xB1, 0x4D, 0x9B, 0xF3, + 0xCD, 0x31, 0x73, 0xB, 0xE9, 0x64, 0xBE, 0xFE, 0x11, 0xFC + }, metadata.GetStream().Id); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TypeSystemRowCounts(bool rebuild) + { + var metadata = GetMetadata(rebuild); var pdbStream = metadata.GetStream(); var tablesStream = metadata.GetStream(); + + Assert.Equal(new uint[] + { + 1, 17, 2, 0, 0, 0, 5, 0, 3, 0, 16, 0, 12, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 1, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, pdbStream.TypeSystemRowCounts); + + Assert.True(tablesStream.HasExternalRowCounts); + Assert.Equal( + tablesStream.ExternalRowCounts.Take((int) TableIndex.MaxTypeSystemTableIndex), + pdbStream.TypeSystemRowCounts.Take((int) TableIndex.MaxTypeSystemTableIndex)); } } } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs index 134cf6375..13b95fb87 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs @@ -1,6 +1,8 @@ using System.IO; +using System.Linq; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.File; using Xunit; @@ -35,12 +37,73 @@ public void PreserveTableStreamNoChange() var peImage = PEImage.FromFile(peFile); var tablesStream = peImage.DotNetDirectory!.Metadata!.GetStream(); + AssertEquivalentAfterRebuild(tablesStream); + } + + [Fact] + public void SmallExternalIndicesShouldHaveSmallIndicesInTablesStream() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(IndexSize.Short, stream.GetIndexEncoder(CodedIndex.HasCustomAttribute).IndexSize); + } + + [Fact] + public void LargeExternalIndicesShouldHaveLargeIndicesInTablesStream() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.LargeIndicesPdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(IndexSize.Long, stream.GetIndexEncoder(CodedIndex.HasCustomAttribute).IndexSize); + } + + [Fact] + public void PreservePdbTableStreamWithSmallExternalIndicesNoChange() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + AssertEquivalentAfterRebuild(pdbMetadata.GetStream()); + } + + [Fact] + public void PreservePdbTableStreamWithLargeExternalIndicesNoChange() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.LargeIndicesPdb); + AssertEquivalentAfterRebuild(pdbMetadata.GetStream()); + } + + [Fact] + public void GetImpliedTableRowCountFromNonPdbMetadataShouldGetLocalRowCount() + { + var peImage = PEImage.FromBytes(Properties.Resources.HelloWorld); + var stream = peImage.DotNetDirectory!.Metadata!.GetStream(); + Assert.Equal((uint) stream.GetTable(TableIndex.TypeDef).Count, stream.GetTableRowCount(TableIndex.TypeDef)); + } + + [Fact] + public void GetImpliedTableRowCountFromPdbMetadataShouldGetExternalRowCount() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(2u, stream.GetTableRowCount(TableIndex.TypeDef)); + Assert.Equal(0u ,(uint) stream.GetTable(TableIndex.TypeDef).Count); + } + + private static void AssertEquivalentAfterRebuild(TablesStream tablesStream) + { using var tempStream = new MemoryStream(); tablesStream.Write(new BinaryStreamWriter(tempStream)); var context = new MetadataReaderContext(VirtualAddressFactory.Instance); var newTablesStream = new SerializedTableStream(context, tablesStream.Name, tempStream.ToArray()); + var metadata = new PE.DotNet.Metadata.Metadata(); + if (tablesStream.HasExternalRowCounts) + { + var pdbStream = new PdbStream(); + pdbStream.UpdateRowCounts(tablesStream.ExternalRowCounts); + metadata.Streams.Add(pdbStream); + } + newTablesStream.Initialize(metadata); + Assert.Equal(tablesStream.Reserved, newTablesStream.Reserved); Assert.Equal(tablesStream.MajorVersion, newTablesStream.MajorVersion); Assert.Equal(tablesStream.MinorVersion, newTablesStream.MinorVersion); @@ -48,15 +111,18 @@ public void PreserveTableStreamNoChange() Assert.Equal(tablesStream.Log2LargestRid, newTablesStream.Log2LargestRid); Assert.Equal(tablesStream.ExtraData, newTablesStream.ExtraData); - for (TableIndex i = 0; i <= TableIndex.GenericParamConstraint; i++) + Assert.All(Enumerable.Range(0, (int) TableIndex.Max), i => { - var oldTable = tablesStream.GetTable(i); - var newTable = newTablesStream.GetTable(i); + var tableIndex = (TableIndex) i; + if (!tableIndex.IsValidTableIndex()) + return; + + var oldTable = tablesStream.GetTable(tableIndex); + var newTable = newTablesStream.GetTable(tableIndex); Assert.Equal(oldTable.Count, newTable.Count); - for (int j = 0; j < oldTable.Count; j++) - Assert.Equal(oldTable[j], newTable[j]); - } + Assert.All(Enumerable.Range(0, oldTable.Count), j => Assert.Equal(oldTable[j], newTable[j])); + }); } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 81aa59c61..df4387877 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -129,6 +129,13 @@ public class Resources { } } + public static byte[] TheAnswerPortablePdb { + get { + object obj = ResourceManager.GetObject("TheAnswerPortablePdb", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] SEHSamples { get { object obj = ResourceManager.GetObject("SEHSamples", resourceCulture); @@ -163,5 +170,12 @@ public class Resources { return ((byte[])(obj)); } } + + public static byte[] LargeIndicesPdb { + get { + object obj = ResourceManager.GetObject("LargeIndicesPdb", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index 54702ef16..50e2489ac 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -54,6 +54,9 @@ ..\Resources\TheAnswer.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\TheAnswer.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\SEHSamples.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -69,4 +72,7 @@ ..\Resources\TlsTest.x64.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\LargeIndicesPdb.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + From 5fb58c7a60ca5235f9399ce8fd7434d986e5a8d5 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 24 Aug 2022 21:24:45 +0200 Subject: [PATCH 128/182] Move table initialization and layouts to separate file. --- .../Metadata/Tables/TablesStream.Tables.cs | 474 ++++++++++++++++++ .../DotNet/Metadata/Tables/TablesStream.cs | 346 +------------ 2 files changed, 475 insertions(+), 345 deletions(-) create mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs new file mode 100644 index 000000000..6d6b28442 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs @@ -0,0 +1,474 @@ +using System.Collections.Generic; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; + +namespace AsmResolver.PE.DotNet.Metadata.Tables +{ + public partial class TablesStream + { + /// + /// Obtains the collection of tables in the tables stream. + /// + /// The tables, including empty tables if there are any. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetTables() + { + var layouts = TableLayouts; + return new IMetadataTable?[] + { + new MetadataTable(TableIndex.Module, layouts[0]), + new MetadataTable(TableIndex.TypeRef, layouts[1]), + new MetadataTable(TableIndex.TypeDef, layouts[2]), + new MetadataTable(TableIndex.FieldPtr, layouts[3]), + new MetadataTable(TableIndex.Field, layouts[4]), + new MetadataTable(TableIndex.Method, layouts[5]), + new MetadataTable(TableIndex.Method, layouts[6]), + new MetadataTable(TableIndex.ParamPtr, layouts[7]), + new MetadataTable(TableIndex.Param, layouts[8]), + new MetadataTable(TableIndex.InterfaceImpl, layouts[9]), + new MetadataTable(TableIndex.MemberRef, layouts[10]), + new MetadataTable(TableIndex.Constant, layouts[11]), + new MetadataTable(TableIndex.CustomAttribute, layouts[12]), + new MetadataTable(TableIndex.FieldMarshal, layouts[13]), + new MetadataTable(TableIndex.DeclSecurity, layouts[14]), + new MetadataTable(TableIndex.ClassLayout, layouts[15]), + new MetadataTable(TableIndex.FieldLayout, layouts[16]), + new MetadataTable(TableIndex.StandAloneSig, layouts[17]), + new MetadataTable(TableIndex.EventMap, layouts[18]), + new MetadataTable(TableIndex.EventPtr, layouts[19]), + new MetadataTable(TableIndex.Event, layouts[20]), + new MetadataTable(TableIndex.PropertyMap, layouts[21]), + new MetadataTable(TableIndex.PropertyPtr, layouts[22]), + new MetadataTable(TableIndex.Property, layouts[23]), + new MetadataTable(TableIndex.MethodSemantics, layouts[24]), + new MetadataTable(TableIndex.MethodImpl, layouts[25]), + new MetadataTable(TableIndex.ModuleRef, layouts[26]), + new MetadataTable(TableIndex.TypeSpec, layouts[27]), + new MetadataTable(TableIndex.ImplMap, layouts[28]), + new MetadataTable(TableIndex.FieldRva, layouts[29]), + new MetadataTable(TableIndex.EncLog, layouts[30]), + new MetadataTable(TableIndex.EncMap, layouts[31]), + new MetadataTable(TableIndex.Assembly, layouts[32]), + new MetadataTable(TableIndex.AssemblyProcessor, layouts[33]), + new MetadataTable(TableIndex.AssemblyOS, layouts[34]), + new MetadataTable(TableIndex.AssemblyRef, layouts[35]), + new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[36]), + new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[37]), + new MetadataTable(TableIndex.File, layouts[38]), + new MetadataTable(TableIndex.ExportedType, layouts[39]), + new MetadataTable(TableIndex.ManifestResource, layouts[40]), + new MetadataTable(TableIndex.NestedClass, layouts[41]), + new MetadataTable(TableIndex.GenericParam, layouts[42]), + new MetadataTable(TableIndex.MethodSpec, layouts[43]), + new MetadataTable(TableIndex.GenericParamConstraint, layouts[44]), + null, + null, + null, + new MetadataTable(TableIndex.Document, layouts[48]), + new MetadataTable(TableIndex.MethodDebugInformation, layouts[49]), + new MetadataTable(TableIndex.LocalScope, layouts[50]), + new MetadataTable(TableIndex.LocalVariable, layouts[51]), + new MetadataTable(TableIndex.LocalConstant, layouts[52]), + new MetadataTable(TableIndex.ImportScope, layouts[53]), + new MetadataTable(TableIndex.StateMachineMethod, layouts[54]), + new MetadataTable(TableIndex.CustomDebugInformation, layouts[55]), + }; + } + + private Dictionary CreateIndexEncoders() + { + return new() + { + [CodedIndex.TypeDefOrRef] = new IndexEncoder(this, + TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec), + + [CodedIndex.HasConstant] = new(this, + TableIndex.Field, TableIndex.Param, TableIndex.Property), + + [CodedIndex.HasCustomAttribute] = new(this, + TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, + TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, + TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, + TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, + TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec), + + [CodedIndex.HasFieldMarshal] = new(this, + TableIndex.Field, TableIndex.Param), + + [CodedIndex.HasDeclSecurity] = new(this, + TableIndex.TypeDef, TableIndex.Method, TableIndex.Assembly), + + [CodedIndex.MemberRefParent] = new(this, + TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef, TableIndex.Method, + TableIndex.TypeSpec), + + [CodedIndex.HasSemantics] = new(this, + TableIndex.Event, TableIndex.Property), + + [CodedIndex.MethodDefOrRef] = new(this, + TableIndex.Method, TableIndex.MemberRef), + + [CodedIndex.MemberForwarded] = new(this, + TableIndex.Field, TableIndex.Method), + + [CodedIndex.Implementation] = new(this, + TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType), + + [CodedIndex.CustomAttributeType] = new(this, + 0, 0, TableIndex.Method, TableIndex.MemberRef, 0), + + [CodedIndex.ResolutionScope] = new(this, + TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef), + + [CodedIndex.TypeOrMethodDef] = new(this, + TableIndex.TypeDef, TableIndex.Method), + + [CodedIndex.HasCustomDebugInformation] = new(this, + TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, TableIndex.Param, + TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, TableIndex.DeclSecurity, + TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, TableIndex.ModuleRef, + TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, TableIndex.File, + TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, + TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) + }; + } + + /// + /// Gets an ordered collection of the current table layouts. + /// + /// The table layouts. + protected TableLayout[] GetTableLayouts() => new[] + { + // Module + new TableLayout( + new ColumnLayout("Generation", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Mvid", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("EncId", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("EncBaseId", ColumnType.Guid, GuidIndexSize)), + + // TypeRef + new TableLayout( + new ColumnLayout("ResolutionScope", ColumnType.ResolutionScope, + GetColumnSize(ColumnType.ResolutionScope)), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.Guid, StringIndexSize)), + + // TypeDef + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), + new ColumnLayout("Extends", ColumnType.TypeDefOrRef, + GetColumnSize(ColumnType.TypeDefOrRef)), + new ColumnLayout("FieldList", ColumnType.Field, GetColumnSize(ColumnType.Field)), + new ColumnLayout("MethodList", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // FieldPtr + new TableLayout( + new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), + + // Field + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // MethodPtr + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // Method + new TableLayout( + new ColumnLayout("RVA", ColumnType.UInt32), + new ColumnLayout("ImplFlags", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("ParamList", ColumnType.Param, GetColumnSize(ColumnType.Param))), + + // ParamPtr + new TableLayout( + new ColumnLayout("Parameter", ColumnType.Param, GetColumnSize(ColumnType.Param))), + + // Parameter + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Sequence", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize)), + + // InterfaceImpl + new TableLayout( + new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("Interface", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // MemberRef + new TableLayout( + new ColumnLayout("Parent", ColumnType.MemberRefParent, GetColumnSize(ColumnType.MemberRefParent)), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // Constant + new TableLayout( + new ColumnLayout("Type", ColumnType.Byte), + new ColumnLayout("Padding", ColumnType.Byte), + new ColumnLayout("Parent", ColumnType.HasConstant, GetColumnSize(ColumnType.HasConstant)), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + + // CustomAttribute + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasCustomAttribute, + GetColumnSize(ColumnType.HasCustomAttribute)), + new ColumnLayout("Type", ColumnType.CustomAttributeType, + GetColumnSize(ColumnType.CustomAttributeType)), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + + // FieldMarshal + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasFieldMarshal, GetColumnSize(ColumnType.HasFieldMarshal)), + new ColumnLayout("NativeType", ColumnType.Blob, BlobIndexSize)), + + // DeclSecurity + new TableLayout( + new ColumnLayout("Action", ColumnType.UInt16), + new ColumnLayout("Parent", ColumnType.HasDeclSecurity, GetColumnSize(ColumnType.HasDeclSecurity)), + new ColumnLayout("PermissionSet", ColumnType.Blob, BlobIndexSize)), + + // ClassLayout + new TableLayout( + new ColumnLayout("PackingSize", ColumnType.UInt16), + new ColumnLayout("ClassSize", ColumnType.UInt32), + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), + + // FieldLayout + new TableLayout( + new ColumnLayout("Offset", ColumnType.UInt32), + new ColumnLayout("Field", ColumnType.TypeDef, GetColumnSize(ColumnType.Field))), + + // StandAloneSig + new TableLayout( + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // EventMap + new TableLayout( + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("EventList", ColumnType.Event, GetColumnSize(ColumnType.Event))), + + // EventPtr + new TableLayout( + new ColumnLayout("Event", ColumnType.Event, GetColumnSize(ColumnType.Event))), + + // Event + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("EventType", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // PropertyMap + new TableLayout( + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("PropertyList", ColumnType.Event, GetColumnSize(ColumnType.Property))), + + // PropertyPtr + new TableLayout( + new ColumnLayout("Property", ColumnType.Property, GetColumnSize(ColumnType.Property))), + + // Property + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("PropertyType", ColumnType.Blob, BlobIndexSize)), + + // MethodSemantics + new TableLayout( + new ColumnLayout("Semantic", ColumnType.UInt16), + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("Association", ColumnType.HasSemantics, GetColumnSize(ColumnType.HasSemantics))), + + // MethodImpl + new TableLayout( + new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("MethodBody", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef)), + new ColumnLayout("MethodDeclaration", ColumnType.MethodDefOrRef, + GetColumnSize(ColumnType.MethodDefOrRef))), + + // ModuleRef + new TableLayout( + new ColumnLayout("Name", ColumnType.String, StringIndexSize)), + + // TypeSpec + new TableLayout( + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // ImplMap + new TableLayout( + new ColumnLayout("MappingFlags", ColumnType.UInt16), + new ColumnLayout("MemberForwarded", ColumnType.MemberForwarded, + GetColumnSize(ColumnType.MemberForwarded)), + new ColumnLayout("ImportName", ColumnType.String, StringIndexSize), + new ColumnLayout("ImportScope", ColumnType.ModuleRef, GetColumnSize(ColumnType.ModuleRef))), + + // FieldRva + new TableLayout( + new ColumnLayout("RVA", ColumnType.UInt32), + new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), + + // EncLog + new TableLayout( + new ColumnLayout("Token", ColumnType.UInt32), + new ColumnLayout("FuncCode", ColumnType.UInt32)), + + // EncMap + new TableLayout( + new ColumnLayout("Token", ColumnType.UInt32)), + + // Assembly + new TableLayout( + new ColumnLayout("HashAlgId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt16), + new ColumnLayout("MinorVersion", ColumnType.UInt16), + new ColumnLayout("BuildNumber", ColumnType.UInt16), + new ColumnLayout("RevisionNumber", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("PublicKey", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Culture", ColumnType.String, StringIndexSize)), + + // AssemblyProcessor + new TableLayout( + new ColumnLayout("Processor", ColumnType.UInt32)), + + // AssemblyOS + new TableLayout( + new ColumnLayout("PlatformId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt32), + new ColumnLayout("MinorVersion", ColumnType.UInt32)), + + // AssemblyRef + new TableLayout( + new ColumnLayout("MajorVersion", ColumnType.UInt16), + new ColumnLayout("MinorVersion", ColumnType.UInt16), + new ColumnLayout("BuildNumber", ColumnType.UInt16), + new ColumnLayout("RevisionNumber", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("PublicKeyOrToken", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Culture", ColumnType.String, StringIndexSize), + new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), + + // AssemblyRefProcessor + new TableLayout( + new ColumnLayout("Processor", ColumnType.UInt32), + new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), + + // AssemblyRefOS + new TableLayout( + new ColumnLayout("PlatformId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt32), + new ColumnLayout("MinorVersion", ColumnType.UInt32), + new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), + + // File + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), + + // ExportedType + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("TypeDefId", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), + new ColumnLayout("Implementation", ColumnType.Implementation, + GetColumnSize(ColumnType.Implementation))), + + // ManifestResource + new TableLayout( + new ColumnLayout("Offset", ColumnType.UInt32), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Implementation", ColumnType.Implementation, + GetColumnSize(ColumnType.Implementation))), + + // NestedClass + new TableLayout( + new ColumnLayout("NestedClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("EnclosingClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), + + // GenericParam + new TableLayout( + new ColumnLayout("Number", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Owner", ColumnType.TypeOrMethodDef, GetColumnSize(ColumnType.TypeOrMethodDef)), + new ColumnLayout("EnclosingClass", ColumnType.String, StringIndexSize)), + + // MethodSpec + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.MethodDefOrRef)), + new ColumnLayout("Instantiation", ColumnType.Blob, BlobIndexSize)), + + // GenericParamConstraint + new TableLayout( + new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)), + new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // Unused + default, + default, + default, + + // Document + new TableLayout( + new ColumnLayout("Name", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("HashAlgorithm", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("Hash", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Language", ColumnType.Guid, GuidIndexSize)), + + // MethodDebugInformation + new TableLayout( + new ColumnLayout("Document", ColumnType.Document, GetColumnSize(ColumnType.Document)), + new ColumnLayout("SequencePoints", ColumnType.Blob, BlobIndexSize)), + + // LocalScope + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("ImportScope", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), + new ColumnLayout("VariableList", ColumnType.LocalVariable, GetColumnSize(ColumnType.LocalVariable)), + new ColumnLayout("ConstantList", ColumnType.LocalConstant, GetColumnSize(ColumnType.LocalConstant)), + new ColumnLayout("StartOffset", ColumnType.UInt32), + new ColumnLayout("Length", ColumnType.UInt32)), + + // LocalVariable + new TableLayout( + new ColumnLayout("Attributes", ColumnType.UInt16), + new ColumnLayout("Index", ColumnType.UInt16), + new ColumnLayout("VariableList", ColumnType.String, StringIndexSize)), + + // LocalConstant + new TableLayout( + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // ImportScope + new TableLayout( + new ColumnLayout("Parent", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), + new ColumnLayout("Imports", ColumnType.Blob, BlobIndexSize)), + + // StateMachineMethod + new TableLayout( + new ColumnLayout("MoveNextMethod", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("KickoffMethod", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // CustomDebugInformation + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasCustomDebugInformation, + GetColumnSize(ColumnType.HasCustomDebugInformation)), + new ColumnLayout("Kind", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + }; + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index bd6c6a73e..af8f7c052 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -10,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Represents the metadata stream containing tables defining each member in a .NET assembly. /// - public class TablesStream : SegmentBase, IMetadataStream + public partial class TablesStream : SegmentBase, IMetadataStream { /// /// The default name of a table stream using the compressed format. @@ -434,124 +434,6 @@ protected static bool IsSorted(ulong sortedMask, TableIndex table) return ((sortedMask >> (int) table) & 1) != 0; } - /// - /// Obtains the collection of tables in the tables stream. - /// - /// The tables, including empty tables if there are any. - /// - /// This method is called upon initialization of the property. - /// - protected virtual IList GetTables() - { - var layouts = TableLayouts; - return new IMetadataTable?[] - { - new MetadataTable(TableIndex.Module, layouts[0]), - new MetadataTable(TableIndex.TypeRef, layouts[1]), - new MetadataTable(TableIndex.TypeDef, layouts[2]), - new MetadataTable(TableIndex.FieldPtr, layouts[3]), - new MetadataTable(TableIndex.Field, layouts[4]), - new MetadataTable(TableIndex.Method, layouts[5]), - new MetadataTable(TableIndex.Method, layouts[6]), - new MetadataTable(TableIndex.ParamPtr, layouts[7]), - new MetadataTable(TableIndex.Param, layouts[8]), - new MetadataTable(TableIndex.InterfaceImpl, layouts[9]), - new MetadataTable(TableIndex.MemberRef, layouts[10]), - new MetadataTable(TableIndex.Constant, layouts[11]), - new MetadataTable(TableIndex.CustomAttribute, layouts[12]), - new MetadataTable(TableIndex.FieldMarshal, layouts[13]), - new MetadataTable(TableIndex.DeclSecurity, layouts[14]), - new MetadataTable(TableIndex.ClassLayout, layouts[15]), - new MetadataTable(TableIndex.FieldLayout, layouts[16]), - new MetadataTable(TableIndex.StandAloneSig, layouts[17]), - new MetadataTable(TableIndex.EventMap, layouts[18]), - new MetadataTable(TableIndex.EventPtr, layouts[19]), - new MetadataTable(TableIndex.Event, layouts[20]), - new MetadataTable(TableIndex.PropertyMap, layouts[21]), - new MetadataTable(TableIndex.PropertyPtr, layouts[22]), - new MetadataTable(TableIndex.Property, layouts[23]), - new MetadataTable(TableIndex.MethodSemantics, layouts[24]), - new MetadataTable(TableIndex.MethodImpl, layouts[25]), - new MetadataTable(TableIndex.ModuleRef, layouts[26]), - new MetadataTable(TableIndex.TypeSpec, layouts[27]), - new MetadataTable(TableIndex.ImplMap, layouts[28]), - new MetadataTable(TableIndex.FieldRva, layouts[29]), - new MetadataTable(TableIndex.EncLog, layouts[30]), - new MetadataTable(TableIndex.EncMap, layouts[31]), - new MetadataTable(TableIndex.Assembly, layouts[32]), - new MetadataTable(TableIndex.AssemblyProcessor, layouts[33]), - new MetadataTable(TableIndex.AssemblyOS, layouts[34]), - new MetadataTable(TableIndex.AssemblyRef, layouts[35]), - new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[36]), - new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[37]), - new MetadataTable(TableIndex.File, layouts[38]), - new MetadataTable(TableIndex.ExportedType, layouts[39]), - new MetadataTable(TableIndex.ManifestResource, layouts[40]), - new MetadataTable(TableIndex.NestedClass, layouts[41]), - new MetadataTable(TableIndex.GenericParam, layouts[42]), - new MetadataTable(TableIndex.MethodSpec, layouts[43]), - new MetadataTable(TableIndex.GenericParamConstraint, layouts[44]), - null, - null, - null, - new MetadataTable(TableIndex.Document, layouts[48]), - new MetadataTable(TableIndex.MethodDebugInformation, layouts[49]), - new MetadataTable(TableIndex.LocalScope, layouts[50]), - new MetadataTable(TableIndex.LocalVariable, layouts[51]), - new MetadataTable(TableIndex.LocalConstant, layouts[52]), - new MetadataTable(TableIndex.ImportScope, layouts[53]), - new MetadataTable(TableIndex.StateMachineMethod, layouts[54]), - new MetadataTable(TableIndex.CustomDebugInformation, layouts[55]), - }; - } - - private Dictionary CreateIndexEncoders() - { - return new() - { - [CodedIndex.TypeDefOrRef] = new IndexEncoder(this, - TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec), - [CodedIndex.HasConstant] = new(this, - TableIndex.Field, TableIndex.Param, TableIndex.Property), - [CodedIndex.HasCustomAttribute] = new(this, - TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, - TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, - TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, - TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, - TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, - TableIndex.GenericParamConstraint, TableIndex.MethodSpec), - [CodedIndex.HasFieldMarshal] = new(this, - TableIndex.Field, TableIndex.Param), - [CodedIndex.HasDeclSecurity] = new(this, - TableIndex.TypeDef, TableIndex.Method, TableIndex.Assembly), - [CodedIndex.MemberRefParent] = new(this, - TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef, - TableIndex.Method, TableIndex.TypeSpec), - [CodedIndex.HasSemantics] = new(this, - TableIndex.Event, TableIndex.Property), - [CodedIndex.MethodDefOrRef] = new(this, - TableIndex.Method, TableIndex.MemberRef), - [CodedIndex.MemberForwarded] = new(this, - TableIndex.Field, TableIndex.Method), - [CodedIndex.Implementation] = new(this, - TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType), - [CodedIndex.CustomAttributeType] = new(this, - 0, 0, TableIndex.Method, TableIndex.MemberRef, 0), - [CodedIndex.ResolutionScope] = new(this, - TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef), - [CodedIndex.TypeOrMethodDef] = new(this, - TableIndex.TypeDef, TableIndex.Method), - [CodedIndex.HasCustomDebugInformation] = new(this, - TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, TableIndex.Param, - TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, TableIndex.DeclSecurity, - TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, TableIndex.ModuleRef, - TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, TableIndex.File, - TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, - TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, - TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) - }; - } - /// /// Gets a table by its table index. /// @@ -628,232 +510,6 @@ protected virtual uint GetColumnSize(ColumnType columnType) /// The encoder. public IndexEncoder GetIndexEncoder(CodedIndex index) => _indexEncoders[index]; - /// - /// Gets an ordered collection of the current table layouts. - /// - /// The table layouts. - protected TableLayout[] GetTableLayouts() - { - var result = new[] - { - new TableLayout( - new ColumnLayout("Generation", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Mvid", ColumnType.Guid, GuidIndexSize), - new ColumnLayout("EncId", ColumnType.Guid, GuidIndexSize), - new ColumnLayout("EncBaseId", ColumnType.Guid, GuidIndexSize)), - new TableLayout( - new ColumnLayout("ResolutionScope", ColumnType.ResolutionScope, - GetColumnSize(ColumnType.ResolutionScope)), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.Guid, StringIndexSize)), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), - new ColumnLayout("Extends", ColumnType.TypeDefOrRef, - GetColumnSize(ColumnType.TypeDefOrRef)), - new ColumnLayout("FieldList", ColumnType.Field, GetColumnSize(ColumnType.Field)), - new ColumnLayout("MethodList", ColumnType.Method, GetColumnSize(ColumnType.Method))), - new TableLayout( - new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method))), - new TableLayout( - new ColumnLayout("RVA", ColumnType.UInt32), - new ColumnLayout("ImplFlags", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("ParamList", ColumnType.Param, GetColumnSize(ColumnType.Param))), - new TableLayout( - new ColumnLayout("Parameter", ColumnType.Param, GetColumnSize(ColumnType.Param))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Sequence", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("Interface", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - new TableLayout( - new ColumnLayout("Parent", ColumnType.MemberRefParent, GetColumnSize(ColumnType.MemberRefParent)), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Type", ColumnType.Byte), - new ColumnLayout("Padding", ColumnType.Byte), - new ColumnLayout("Parent", ColumnType.HasConstant, GetColumnSize(ColumnType.HasConstant)), - new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.HasCustomAttribute, GetColumnSize(ColumnType.HasCustomAttribute)), - new ColumnLayout("Type", ColumnType.CustomAttributeType, GetColumnSize(ColumnType.CustomAttributeType)), - new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.HasFieldMarshal, GetColumnSize(ColumnType.HasFieldMarshal)), - new ColumnLayout("NativeType", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Action", ColumnType.UInt16), - new ColumnLayout("Parent", ColumnType.HasDeclSecurity, GetColumnSize(ColumnType.HasDeclSecurity)), - new ColumnLayout("PermissionSet", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("PackingSize", ColumnType.UInt16), - new ColumnLayout("ClassSize", ColumnType.UInt32), - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), - new TableLayout( - new ColumnLayout("Offset", ColumnType.UInt32), - new ColumnLayout("Field", ColumnType.TypeDef, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("EventList", ColumnType.Event, GetColumnSize(ColumnType.Event))), - new TableLayout( - new ColumnLayout("Event", ColumnType.Event, GetColumnSize(ColumnType.Event))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("EventType", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - new TableLayout( - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("PropertyList", ColumnType.Event, GetColumnSize(ColumnType.Property))), - new TableLayout( - new ColumnLayout("Property", ColumnType.Property, GetColumnSize(ColumnType.Property))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("PropertyType", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Semantic", ColumnType.UInt16), - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), - new ColumnLayout("Association", ColumnType.HasSemantics, GetColumnSize(ColumnType.HasSemantics))), - new TableLayout( - new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("MethodBody", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef)), - new ColumnLayout("MethodDeclaration", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef))), - new TableLayout( - new ColumnLayout("Name", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("MappingFlags", ColumnType.UInt16), - new ColumnLayout("MemberForwarded", ColumnType.MemberForwarded, GetColumnSize(ColumnType.MemberForwarded)), - new ColumnLayout("ImportName", ColumnType.String, StringIndexSize), - new ColumnLayout("ImportScope", ColumnType.ModuleRef, GetColumnSize(ColumnType.ModuleRef))), - new TableLayout( - new ColumnLayout("RVA", ColumnType.UInt32), - new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Token", ColumnType.UInt32), - new ColumnLayout("FuncCode", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("Token", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("HashAlgId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt16), - new ColumnLayout("MinorVersion", ColumnType.UInt16), - new ColumnLayout("BuildNumber", ColumnType.UInt16), - new ColumnLayout("RevisionNumber", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("PublicKey", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Culture", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Processor", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("PlatformId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt32), - new ColumnLayout("MinorVersion", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("MajorVersion", ColumnType.UInt16), - new ColumnLayout("MinorVersion", ColumnType.UInt16), - new ColumnLayout("BuildNumber", ColumnType.UInt16), - new ColumnLayout("RevisionNumber", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("PublicKeyOrToken", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Culture", ColumnType.String, StringIndexSize), - new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Processor", ColumnType.UInt32), - new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), - new TableLayout( - new ColumnLayout("PlatformId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt32), - new ColumnLayout("MinorVersion", ColumnType.UInt32), - new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("TypeDefId", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), - new ColumnLayout("Implementation", ColumnType.Implementation, GetColumnSize(ColumnType.Implementation))), - new TableLayout( - new ColumnLayout("Offset", ColumnType.UInt32), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Implementation", ColumnType.Implementation, GetColumnSize(ColumnType.Implementation))), - new TableLayout( - new ColumnLayout("NestedClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("EnclosingClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), - new TableLayout( - new ColumnLayout("Number", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Owner", ColumnType.TypeOrMethodDef, GetColumnSize(ColumnType.TypeOrMethodDef)), - new ColumnLayout("EnclosingClass", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.MethodDefOrRef)), - new ColumnLayout("Instantiation", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)), - new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - default, - default, - default, - new TableLayout( - new ColumnLayout("Name", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("HashAlgorithm", ColumnType.Guid, GuidIndexSize), - new ColumnLayout("Hash", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("Language", ColumnType.Guid, GuidIndexSize)), - new TableLayout( - new ColumnLayout("Document", ColumnType.Document, GetColumnSize(ColumnType.Document)), - new ColumnLayout("SequencePoints", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), - new ColumnLayout("ImportScope", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), - new ColumnLayout("VariableList", ColumnType.LocalVariable, GetColumnSize(ColumnType.LocalVariable)), - new ColumnLayout("ConstantList", ColumnType.LocalConstant, GetColumnSize(ColumnType.LocalConstant)), - new ColumnLayout("StartOffset", ColumnType.UInt32), - new ColumnLayout("Length", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("Attributes", ColumnType.UInt16), - new ColumnLayout("Index", ColumnType.UInt16), - new ColumnLayout("VariableList", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), - new ColumnLayout("Imports", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("MoveNextMethod", ColumnType.Method, GetColumnSize(ColumnType.Method)), - new ColumnLayout("KickoffMethod", ColumnType.Method, GetColumnSize(ColumnType.Method))), - new TableLayout( - new ColumnLayout("Parent", ColumnType.HasCustomDebugInformation, GetColumnSize(ColumnType.HasCustomDebugInformation)), - new ColumnLayout("Kind", ColumnType.Guid, GuidIndexSize), - new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), - }; - - return result; - } - /// /// Gets the range of metadata tokens referencing fields that a type defines. /// From f0d16059f6fa0b456177ddec44b7505ea8b193ab Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 24 Aug 2022 21:39:29 +0200 Subject: [PATCH 129/182] Add IMetadataTable.IsSorted. --- .../DotNet/Metadata/Tables/IMetadataTable.cs | 9 ++++++ .../DotNet/Metadata/Tables/MetadataTable.cs | 19 +++++++++++ .../Tables/SerializedMetadataTable.cs | 14 ++++++-- .../Metadata/Tables/SerializedTableStream.cs | 2 ++ .../Metadata/Tables/TablesStream.Tables.cs | 32 +++++++++---------- .../DotNet/Metadata/Tables/TablesStream.cs | 12 +++++-- .../Metadata/Tables/TablesStreamTest.cs | 1 + 7 files changed, 67 insertions(+), 22 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs index 353a82bc3..59b6520f6 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs @@ -37,6 +37,15 @@ IndexSize IndexSize set; } + /// + /// Gets or sets a value indicating whether the table is considered sorted. + /// + bool IsSorted + { + get; + set; + } + /// /// Gets the contents of a row by its row identifier. /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs index 94983d7d9..aaaea27c0 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs @@ -25,9 +25,21 @@ public class MetadataTable : IMetadataTable, ICollection /// The index of the table. /// The layout of the table. public MetadataTable(TableIndex tableIndex, TableLayout layout) + : this(tableIndex, layout, false) + { + } + + /// + /// Creates a new metadata table using the provided layout. + /// + /// The index of the table. + /// The layout of the table. + /// Indicates the table is sorted or not. + public MetadataTable(TableIndex tableIndex, TableLayout layout, bool isSorted) { TableIndex = tableIndex; Layout = layout; + IsSorted = isSorted; } /// @@ -55,6 +67,13 @@ public TableLayout Layout set => Rows[index] = value; } + /// + public bool IsSorted + { + get; + set; + } + /// IMetadataRow IMetadataTable.this[int index] { diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs index 33255673b..b39575f70 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs @@ -42,9 +42,15 @@ public class SerializedMetadataTable : MetadataTable /// The input stream. /// The index of the table. /// The layout of the table. + /// Indicates the table is sorted or not. /// The method to use for reading each row in the table. - public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableIndex, TableLayout originalLayout, ReadRowDelegate readRow) - : base(tableIndex, originalLayout) + public SerializedMetadataTable( + in BinaryStreamReader reader, + TableIndex tableIndex, + TableLayout originalLayout, + bool isSorted, + ReadRowDelegate readRow) + : base(tableIndex, originalLayout, isSorted) { _reader = reader; _originalLayout = originalLayout; @@ -59,14 +65,16 @@ public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableInd /// The input stream. /// The index of the table. /// The layout of the table. + /// Indicates the table is sorted or not. /// The method to use for reading each row in the table. public SerializedMetadataTable( MetadataReaderContext context, in BinaryStreamReader reader, TableIndex tableIndex, TableLayout originalLayout, + bool isSorted, ReadRowExtendedDelegate readRow) - : this(reader, tableIndex, originalLayout, + : this(reader, tableIndex, originalLayout, isSorted, (ref BinaryStreamReader r, TableLayout l) => readRow(context, ref r, l)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs index fcddfad97..8608952fc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs @@ -306,6 +306,7 @@ private BinaryStreamReader CreateNextRawTableReader(TableIndex currentIndex, ref CreateNextRawTableReader(index, ref offset), index, TableLayouts[(int) index], + IsSorted(_sortedMask, index), readRow); } @@ -320,6 +321,7 @@ private BinaryStreamReader CreateNextRawTableReader(TableIndex currentIndex, ref CreateNextRawTableReader(index, ref offset), index, TableLayouts[(int) index], + IsSorted(_sortedMask, index), readRow); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs index 6d6b28442..877d34129 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs @@ -26,14 +26,14 @@ public partial class TablesStream new MetadataTable(TableIndex.Method, layouts[6]), new MetadataTable(TableIndex.ParamPtr, layouts[7]), new MetadataTable(TableIndex.Param, layouts[8]), - new MetadataTable(TableIndex.InterfaceImpl, layouts[9]), + new MetadataTable(TableIndex.InterfaceImpl, layouts[9], true), new MetadataTable(TableIndex.MemberRef, layouts[10]), - new MetadataTable(TableIndex.Constant, layouts[11]), - new MetadataTable(TableIndex.CustomAttribute, layouts[12]), - new MetadataTable(TableIndex.FieldMarshal, layouts[13]), - new MetadataTable(TableIndex.DeclSecurity, layouts[14]), - new MetadataTable(TableIndex.ClassLayout, layouts[15]), - new MetadataTable(TableIndex.FieldLayout, layouts[16]), + new MetadataTable(TableIndex.Constant, layouts[11], true), + new MetadataTable(TableIndex.CustomAttribute, layouts[12], true), + new MetadataTable(TableIndex.FieldMarshal, layouts[13], true), + new MetadataTable(TableIndex.DeclSecurity, layouts[14], true), + new MetadataTable(TableIndex.ClassLayout, layouts[15], true), + new MetadataTable(TableIndex.FieldLayout, layouts[16], true), new MetadataTable(TableIndex.StandAloneSig, layouts[17]), new MetadataTable(TableIndex.EventMap, layouts[18]), new MetadataTable(TableIndex.EventPtr, layouts[19]), @@ -41,12 +41,12 @@ public partial class TablesStream new MetadataTable(TableIndex.PropertyMap, layouts[21]), new MetadataTable(TableIndex.PropertyPtr, layouts[22]), new MetadataTable(TableIndex.Property, layouts[23]), - new MetadataTable(TableIndex.MethodSemantics, layouts[24]), - new MetadataTable(TableIndex.MethodImpl, layouts[25]), + new MetadataTable(TableIndex.MethodSemantics, layouts[24], true), + new MetadataTable(TableIndex.MethodImpl, layouts[25], true), new MetadataTable(TableIndex.ModuleRef, layouts[26]), new MetadataTable(TableIndex.TypeSpec, layouts[27]), - new MetadataTable(TableIndex.ImplMap, layouts[28]), - new MetadataTable(TableIndex.FieldRva, layouts[29]), + new MetadataTable(TableIndex.ImplMap, layouts[28], true), + new MetadataTable(TableIndex.FieldRva, layouts[29], true), new MetadataTable(TableIndex.EncLog, layouts[30]), new MetadataTable(TableIndex.EncMap, layouts[31]), new MetadataTable(TableIndex.Assembly, layouts[32]), @@ -58,21 +58,21 @@ public partial class TablesStream new MetadataTable(TableIndex.File, layouts[38]), new MetadataTable(TableIndex.ExportedType, layouts[39]), new MetadataTable(TableIndex.ManifestResource, layouts[40]), - new MetadataTable(TableIndex.NestedClass, layouts[41]), - new MetadataTable(TableIndex.GenericParam, layouts[42]), + new MetadataTable(TableIndex.NestedClass, layouts[41], true), + new MetadataTable(TableIndex.GenericParam, layouts[42], true), new MetadataTable(TableIndex.MethodSpec, layouts[43]), - new MetadataTable(TableIndex.GenericParamConstraint, layouts[44]), + new MetadataTable(TableIndex.GenericParamConstraint, layouts[44], true), null, null, null, new MetadataTable(TableIndex.Document, layouts[48]), new MetadataTable(TableIndex.MethodDebugInformation, layouts[49]), - new MetadataTable(TableIndex.LocalScope, layouts[50]), + new MetadataTable(TableIndex.LocalScope, layouts[50], true), new MetadataTable(TableIndex.LocalVariable, layouts[51]), new MetadataTable(TableIndex.LocalConstant, layouts[52]), new MetadataTable(TableIndex.ImportScope, layouts[53]), new MetadataTable(TableIndex.StateMachineMethod, layouts[54]), - new MetadataTable(TableIndex.CustomDebugInformation, layouts[55]), + new MetadataTable(TableIndex.CustomDebugInformation, layouts[55], true), }; } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index af8f7c052..65353ffb3 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -324,8 +324,8 @@ public override void Write(IBinaryStreamWriter writer) protected virtual ulong ComputeValidBitmask() { // TODO: make more configurable (maybe add IMetadataTable.IsPresent property?). - ulong result = 0; + for (int i = 0; i < Tables.Count; i++) { if (Tables[i]?.Count > 0) @@ -342,9 +342,15 @@ protected virtual ulong ComputeValidBitmask() /// The valid bitmask. protected virtual ulong ComputeSortedBitmask() { - // TODO: make more configurable (maybe add IMetadataTable.IsSorted property?). + ulong result = 0; - return 0x000016003301FA00; + for (int i = 0; i < Tables.Count; i++) + { + if (Tables[i]?.IsSorted ?? false) + result |= 1UL << i; + } + + return result; } /// diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs index 13b95fb87..e207837f3 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs @@ -120,6 +120,7 @@ private static void AssertEquivalentAfterRebuild(TablesStream tablesStream) var oldTable = tablesStream.GetTable(tableIndex); var newTable = newTablesStream.GetTable(tableIndex); + Assert.Equal(oldTable.IsSorted, newTable.IsSorted); Assert.Equal(oldTable.Count, newTable.Count); Assert.All(Enumerable.Range(0, oldTable.Count), j => Assert.Equal(oldTable[j], newTable[j])); }); From b2ea332eb1f2cdf95291cce1627e988117ebca06 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 24 Aug 2022 22:02:08 +0200 Subject: [PATCH 130/182] Include missing test PDB files. --- test/AsmResolver.PE.Tests/Resources/.gitignore | 1 + .../Resources/HelloWorld.pdb | Bin 0 -> 10104 bytes .../Resources/LargeIndicesPdb.pdb | Bin 0 -> 25156 bytes .../Resources/TheAnswer.pdb | Bin 0 -> 9720 bytes 4 files changed, 1 insertion(+) create mode 100644 test/AsmResolver.PE.Tests/Resources/.gitignore create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.pdb create mode 100644 test/AsmResolver.PE.Tests/Resources/LargeIndicesPdb.pdb create mode 100644 test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb diff --git a/test/AsmResolver.PE.Tests/Resources/.gitignore b/test/AsmResolver.PE.Tests/Resources/.gitignore new file mode 100644 index 000000000..bd46a47b5 --- /dev/null +++ b/test/AsmResolver.PE.Tests/Resources/.gitignore @@ -0,0 +1 @@ +!*.pdb diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.pdb b/test/AsmResolver.PE.Tests/Resources/HelloWorld.pdb new file mode 100644 index 0000000000000000000000000000000000000000..aba7e1f7882e40852bd92212ac97328111c295ab GIT binary patch literal 10104 zcmaJ{2V4_L7hk|bQ2{;9ex3-}4v2vWij^dUgc5=jvEAX4Y#@+kvZ1LMLdbyCSj6G6sUCVX!{n-WQb##KCgx7Me*s^Y?|E zv3qCmp6Txfcqp;o9pkXyG@%4AGd1(OJwo2hNIe#Ko3e1ontqO+FEA2A<`4)n!ro)| zkSTWQ;}`gCf?YsQa1Wk?5B3dyVxJuK7ql8O!0y0%@PfGa#eM~1PtRcAwb=JyJw3=u zPapf$h-n3A4X{hiY5;hEee|HW;{cxt*gJ;a1k7F^z)tfqy*4SKbg&sMetm?=n$&`! z9YJ&FobP*c@D^XFl9Q>w@0XX~j$PXj=f3@}YtZU4kK6s2>=?1pV$Z_6mj|5;MU5eS z`t)D?<%mrF3za7li2J%A@-S2)9YB?fd=R-r7>>yLpoknD=!gBG3B<5WfXEG9B&ZjH z_GckNp(H>e6Y~0d%Ot@vSVZE=2Q1d>(|^!#t4WTtSmv`U{ayv zIeMt?iN$_TysMZ-iS~X5lAzGOK8O&3<%ppeK^;kC63L6(!FHGOY}jojyO*FA9UM9CET*m;+W zkl|;xq`AFV5g?Bp*`>IKshgND(aG+EdDtS~={JntT^#f0m9{l+#xLD}Jd*o7Zv5(! zt3`|o^T+Y424G$SYC`r9S0a)MgowmCT#s=)}Wb^6l;Hg6z4F5Ech3;9vxzTS9V)LdHdG`50$gu0$b5Aqpf> zpi%{D8zvC*Ai6}1jTP{iP{4&zpo&C_3Pgfv^$(fa!;n-6qkM@>1X0CNbg2T8VXOoq zM58S5vV-+hGK^4o{3);_pTc#r=Lb0kQS2OGK6eV)0paoN$b3F!n!{8ljfxt)9T^K< z9>N!h5y;PnN@v*kFgzJlUxp1frr>f6B!e$H$A>}n_6D~Of#g7rPaqR};K%U6X?i@t z$?bZPYF^msM%e!dVITNIguQN7lpnF8bYlhWU(XhBWfHlBkCN!VA+StJ;t7RVBsX<_ zc?BsU=)($w@C7SE=_fcHwO+SAXpzqo$Q7`V2FnFp5(oes_%LnTe#l%88ql!Zrt)AC zC+Sgvd4q7~$SyhBK((-nfwD>{rle}fvBtZq? zSSYE1_S<(n0YDF?$+YArlbM_R`hB<23$^YE^g)6Ja#R+D!}O2w}}oORU2J0zmrX=HDAyBM#)h`M8ak|Hs?_!62-mprr`6j7{!ULXI7;EV%NIhs`{(|j}uO&w?z68jyN%0A`~KA5Jhrq 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 E0kiW!bpQYW literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb b/test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb new file mode 100644 index 0000000000000000000000000000000000000000..479dfee18707ae4767ecd6904fd57678847bf537 GIT binary patch literal 9720 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 GCGvku?l1BH literal 0 HcmV?d00001 From f1017bf9cfa7ec05624558c4a7f853bb79e4cd8c Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 25 Aug 2022 17:37:32 +0200 Subject: [PATCH 131/182] Only include sorted bits of type system or pdb tables if tables stream contains these tables. --- .../DotNet/Metadata/Tables/TablesStream.cs | 27 ++++++++++++++++++- .../DotNet/Metadata/MetadataTest.cs | 5 ++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index 65353ffb3..8c6089ad1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -344,12 +344,37 @@ protected virtual ulong ComputeSortedBitmask() { ulong result = 0; + bool containsTypeSystemData = false; + bool containsPdbData = false; + + // Determine which tables are marked as sorted. for (int i = 0; i < Tables.Count; i++) { - if (Tables[i]?.IsSorted ?? false) + if (Tables[i] is not { } table) + continue; + + if (table.IsSorted) result |= 1UL << i; + + if (table.Count > 0) + { + if (i <= (int) TableIndex.MaxTypeSystemTableIndex) + containsTypeSystemData = true; + else + containsPdbData = true; + } } + const ulong typeSystemMask = (1UL << (int) TableIndex.MaxTypeSystemTableIndex + 1) - 1; + const ulong pdbMask = ((1UL << (int) TableIndex.Max) - 1) & ~typeSystemMask; + + // Backwards compatibility: Ensure that only the bits are set in the sorted mask if the metadata + // actually contains the type system and/or pdb tables. + if (!containsTypeSystemData) + result &= ~typeSystemMask; + if (!containsPdbData) + result &= ~pdbMask; + return result; } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs index 14441d2a0..e7f8dc2f5 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs @@ -114,7 +114,7 @@ public void PreserveMetadataNoChange() Assert.Equal(metadata.Flags, newMetadata.Flags); Assert.Equal(metadata.Streams.Count, newMetadata.Streams.Count); - for (int i = 0; i < metadata.Streams.Count; i++) + Assert.All(Enumerable.Range(0, metadata.Streams.Count), i => { var oldStream = metadata.Streams[i]; var newStream = newMetadata.Streams[i]; @@ -123,8 +123,7 @@ public void PreserveMetadataNoChange() var oldData = oldStream.CreateReader().ReadToEnd(); var newData = newStream.CreateReader().ReadToEnd(); Assert.Equal(oldData, newData); - - } + }); } From 558cff90a46ccb359e1254c7a251bf2ee92ffe49 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 25 Aug 2022 20:24:51 +0200 Subject: [PATCH 132/182] Move dynamic local variable signature resolution to DynamicMethodHelper to avoid trim warnings. --- .../DynamicMethodHelper.cs | 65 +++++++++++++++- .../Signatures/Types/TypeSignature.cs | 28 +------ src/AsmResolver/IO/BinaryStreamReader.cs | 4 + .../DynamicMethodDefinitionTest.cs | 75 ++++++++++--------- .../TDynamicMethod.cs | 11 ++- 5 files changed, 117 insertions(+), 66 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index 283a2b7b5..118fd49ac 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -6,22 +6,37 @@ using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Dynamic { internal static class DynamicMethodHelper { + private static readonly MethodInfo GetTypeFromHandleUnsafeMethod; + + static DynamicMethodHelper() + { + // We need to use reflection for this to stay compatible with .netstandard 2.0. + GetTypeFromHandleUnsafeMethod = typeof(Type) + .GetMethod("GetTypeFromHandleUnsafe", + (BindingFlags) (-1), + null, + new[] {typeof(IntPtr)}, + null)!; + } + public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition method, byte[] localSig) { if (!(method.Module is SerializedModuleDefinition module)) throw new ArgumentException("Method body should reference a serialized module."); var reader = ByteArrayDataSource.CreateReader(localSig); - if (CallingConventionSignature.FromReader( - new BlobReadContext(module.ReaderContext), - ref reader) is not LocalVariablesSignature localsSignature) + if (ReadLocalVariableSignature( + new BlobReadContext(module.ReaderContext), + ref reader) is not LocalVariablesSignature localsSignature) { throw new ArgumentException("Invalid local variables signature."); } @@ -30,6 +45,50 @@ public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition methodBody.LocalVariables.Add(new CilLocalVariable(localsSignature.VariableTypes[i])); } + private static TypeSignature ReadTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) + { + return (ElementType) reader.PeekByte() == ElementType.Internal + ? ReadInternalTypeSignature(context, ref reader) + : TypeSignature.FromReader(in context, ref reader); + } + + private static TypeSignature ReadInternalTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) + { + var address = IntPtr.Size switch + { + 4 => new IntPtr(reader.ReadInt32()), + _ => new IntPtr(reader.ReadInt64()) + }; + + // Let the runtime translate the address to a type and import it. + var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address }); + + var type = clrType is not null + ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType) + : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); + + return new TypeDefOrRefSignature(type); + } + + private static LocalVariablesSignature ReadLocalVariableSignature( + in BlobReadContext context, + ref BinaryStreamReader reader) + { + var result = new LocalVariablesSignature(); + result.Attributes = (CallingConventionAttributes) reader.ReadByte(); + + if (!reader.TryReadCompressedUInt32(out uint count)) + { + context.ReaderContext.BadImage("Invalid number of local variables in local variable signature."); + return result; + } + + for (int i = 0; i < count; i++) + result.VariableTypes.Add(ReadTypeSignature(context, ref reader)); + + return result; + } + public static void ReadReflectionExceptionHandlers(CilMethodBody methodBody, IList? ehInfos, byte[] ehHeader, ReferenceImporter importer) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 389411e30..7eb81bfbd 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -14,18 +14,6 @@ public abstract class TypeSignature : ExtendableBlobSignature, ITypeDescriptor { internal const string NullTypeToString = "<>"; - private static readonly MethodInfo GetTypeFromHandleUnsafeMethod; - - static TypeSignature() - { - GetTypeFromHandleUnsafeMethod = typeof(Type) - .GetMethod("GetTypeFromHandleUnsafe", - (BindingFlags) (-1), - null, - new[] {typeof(IntPtr)}, - null)!; - } - /// public abstract string? Name { @@ -156,19 +144,9 @@ public static TypeSignature FromReader(in BlobReadContext context, ref BinaryStr return new BoxedTypeSignature(FromReader(context, ref reader)); case ElementType.Internal: - var address = IntPtr.Size switch - { - 4 => new IntPtr(reader.ReadInt32()), - _ => new IntPtr(reader.ReadInt64()) - }; - - // Let the runtime translate the address to a type and import it. - var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address }); - var asmResType = clrType is not null - ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType) - : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); - - return new TypeDefOrRefSignature(asmResType); + throw new NotSupportedException( + "Encountered an COR_ELEMENT_TYPE_INTERNAL type signature which is not supported by this " + + " type signature reader. Use the AsmResolver.DotNet.Dynamic extension package instead."); default: throw new ArgumentOutOfRangeException($"Invalid or unsupported element type {elementType}."); diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index 9de2fd993..d0a101c3d 100644 --- a/src/AsmResolver/IO/BinaryStreamReader.cs +++ b/src/AsmResolver/IO/BinaryStreamReader.cs @@ -134,6 +134,10 @@ private void AssertCanRead(uint count) throw new EndOfStreamException(); } + public int PeekByte() => CanRead(1) + ? DataSource[Offset] + : -1; + /// /// Reads a single byte from the input stream, and advances the current offset by one. /// diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index 6d889cb60..7d3f91e09 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -14,56 +14,63 @@ public class DynamicMethodDefinitionTest public void ReadDynamicMethod() { var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); + var generatedDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); + var dynamicMethodDef = new DynamicMethodDefinition(module, generatedDynamicMethod); - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - var dynamicMethodDefinition = new DynamicMethodDefinition(module, generateDynamicMethod); - - Assert.NotNull(dynamicMethodDefinition); - - Assert.NotEmpty(dynamicMethodDefinition.CilMethodBody.Instructions); - - Assert.Equal(dynamicMethodDefinition.CilMethodBody.Instructions.Select(q=>q.OpCode),new [] + Assert.NotNull(dynamicMethodDef); + Assert.NotEmpty(dynamicMethodDef.CilMethodBody!.Instructions); + Assert.Equal(new[] { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - Assert.Equal(dynamicMethodDefinition.Parameters.Select(q=>q.ParameterType),new TypeSignature[] + CilCode.Ldarg_0, + CilCode.Stloc_0, + CilCode.Ldloc_0, + CilCode.Call, + CilCode.Ldarg_1, + CilCode.Ret + }, dynamicMethodDef.CilMethodBody.Instructions.Select(q => q.OpCode.Code)); + Assert.Equal(new TypeSignature[] { module.CorLibTypeFactory.String, module.CorLibTypeFactory.Int32 - }); + }, dynamicMethodDef.Parameters.Select(q => q.ParameterType)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + }, dynamicMethodDef.CilMethodBody.LocalVariables.Select(v => v.VariableType)); } - + [Fact] public void RtDynamicMethod() { var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - var rtDynamicMethod = generateDynamicMethod.GetType().GetField("m_dynMethod", (BindingFlags) (-1))?.GetValue(generateDynamicMethod); - - var dynamicMethodDefinition = new DynamicMethodDefinition(module, rtDynamicMethod); - - Assert.NotNull(dynamicMethodDefinition); + var generatedDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); + object rtDynamicMethod = generatedDynamicMethod + .GetType() + .GetField("m_dynMethod", (BindingFlags) (-1))? + .GetValue(generatedDynamicMethod); + var dynamicMethod = new DynamicMethodDefinition(module, rtDynamicMethod!); - Assert.NotEmpty(dynamicMethodDefinition.CilMethodBody.Instructions); - - Assert.Equal(dynamicMethodDefinition.CilMethodBody.Instructions.Select(q=>q.OpCode),new [] + Assert.NotNull(dynamicMethod); + Assert.NotEmpty(dynamicMethod.CilMethodBody!.Instructions); + Assert.Equal(new[] { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - Assert.Equal(dynamicMethodDefinition.Parameters.Select(q=>q.ParameterType),new TypeSignature[] + CilCode.Ldarg_0, + CilCode.Stloc_0, + CilCode.Ldloc_0, + CilCode.Call, + CilCode.Ldarg_1, + CilCode.Ret + }, dynamicMethod.CilMethodBody.Instructions.Select(q => q.OpCode.Code)); + Assert.Equal(new TypeSignature[] { module.CorLibTypeFactory.String, module.CorLibTypeFactory.Int32 - }); + }, dynamicMethod.Parameters.Select(q => q.ParameterType)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + }, dynamicMethod.CilMethodBody.LocalVariables.Select(v => v.VariableType)); } } } diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/TDynamicMethod.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/TDynamicMethod.cs index 3cd849478..9890d227b 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/TDynamicMethod.cs +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/TDynamicMethod.cs @@ -14,7 +14,7 @@ public static DynamicMethod GenerateDynamicMethod() // of Integer, and two parameters whose types are specified by // the array helloArgs. Create the method in the module that // defines the String class. - DynamicMethod hello = new DynamicMethod("Hello", + var hello = new DynamicMethod("Hello", typeof(int), helloArgs, typeof(string).Module); @@ -24,15 +24,18 @@ public static DynamicMethod GenerateDynamicMethod() Type[] writeStringArgs = {typeof(string)}; // Get the overload of Console.WriteLine that has one // String parameter. - MethodInfo writeString = typeof(Console).GetMethod("WriteLine", + var writeString = typeof(Console).GetMethod("WriteLine", writeStringArgs); // Get an ILGenerator and emit a body for the dynamic method, // using a stream size larger than the IL that will be // emitted. - ILGenerator il = hello.GetILGenerator(256); + var il = hello.GetILGenerator(256); + il.DeclareLocal(typeof(string)); // Load the first argument, which is a string, onto the stack. il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldloc_0); // Call the overload of Console.WriteLine that prints a string. il.EmitCall(OpCodes.Call, writeString, null); // The Hello method returns the value of the second argument; @@ -50,4 +53,4 @@ public static DynamicMethod GenerateDynamicMethod() } } -} \ No newline at end of file +} From 4fc7250310888e39302225cd8dde99758cae4bf9 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 25 Aug 2022 22:27:53 +0200 Subject: [PATCH 133/182] Reduce compiler warnings. --- src/AsmResolver.DotNet/AssemblyDescriptor.cs | 1 - src/AsmResolver.PE/Tls/TlsDirectory.cs | 1 + src/AsmResolver/IO/BinaryStreamReader.cs | 4 ++++ test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs | 2 +- test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs | 6 ++++-- test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs | 3 ++- .../Leaves/EnumTypeRecordTest.cs | 2 +- test/AsmResolver.Tests/Utf8StringTest.cs | 2 +- 8 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs index 5d19e5b42..ed22fb622 100644 --- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs @@ -240,7 +240,6 @@ protected static byte[] ComputePublicKeyToken(byte[] publicKey, AssemblyHashAlgo AssemblyHashAlgorithm.None => SHA1.Create(), // Default algo is SHA-1. AssemblyHashAlgorithm.Md5 => MD5.Create(), AssemblyHashAlgorithm.Sha1 => SHA1.Create(), - AssemblyHashAlgorithm.Hmac => HMAC.Create(), AssemblyHashAlgorithm.Sha256 => SHA256.Create(), AssemblyHashAlgorithm.Sha384 => SHA384.Create(), AssemblyHashAlgorithm.Sha512 => SHA512.Create(), diff --git a/src/AsmResolver.PE/Tls/TlsDirectory.cs b/src/AsmResolver.PE/Tls/TlsDirectory.cs index b6bd730b1..af563f6d7 100644 --- a/src/AsmResolver.PE/Tls/TlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/TlsDirectory.cs @@ -63,6 +63,7 @@ public TlsCharacteristics Characteristics set; } + /// public override void UpdateOffsets(in RelocationParameters parameters) { _imageBase = parameters.ImageBase; diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index d0a101c3d..9034b714b 100644 --- a/src/AsmResolver/IO/BinaryStreamReader.cs +++ b/src/AsmResolver/IO/BinaryStreamReader.cs @@ -134,6 +134,10 @@ private void AssertCanRead(uint count) throw new EndOfStreamException(); } + /// + /// Peeks a single byte from the input stream. + /// + /// The read byte, or -1 if no byte could be read. public int PeekByte() => CanRead(1) ? DataSource[Offset] : -1; diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 423509378..e97dd755b 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -237,7 +237,7 @@ public void SameManifestContentsShouldResultInSameBundleID() private static string FindAppHostTemplate(string sdkVersion) { string sdkPath = Path.Combine(DotNetCorePathProvider.DefaultInstallationPath!, "sdk"); - string? sdkVersionPath = null; + string sdkVersionPath = null; foreach (string dir in Directory.GetDirectories(sdkPath)) { if (Path.GetFileName(dir).StartsWith(sdkVersion)) diff --git a/test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs b/test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs index a685375e6..074e4f4b8 100644 --- a/test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs +++ b/test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs @@ -64,8 +64,10 @@ public void PersistentDataReader() module.Write(stream); var newModule = ModuleDefinition.FromBytes(stream.ToArray()); - Assert.Equal(contents - , newModule.Resources.First(r => r.Name == resourceName).GetReader()?.ReadToEnd()); + var resource = newModule.Resources.First(r => r.Name == resourceName); + + Assert.True(resource.TryGetReader(out var reader)); + Assert.Equal(contents, reader.ReadToEnd()); } } } diff --git a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs index 6426cb5b4..b60f1ed9d 100644 --- a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Linq; using AsmResolver.IO; @@ -24,7 +25,7 @@ public void ReadExportNames() { "NamedExport1", "NamedExport2", - }, image.Exports?.Entries.Select(e => e.Name)); + }, image.Exports?.Entries.Select(e => e.Name) ?? Array.Empty()); } [Fact] diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs index 5bff46dd0..5911ceb5e 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs @@ -17,7 +17,7 @@ public EnumTypeRecordTest(MockPdbFixture fixture) public void FieldList() { var leaf = _fixture.SimplePdb.GetLeafRecord(0x1009); - var fields = Assert.IsAssignableFrom(leaf).Fields.Entries.Cast().ToArray(); + var fields = Assert.IsAssignableFrom(leaf).Fields!.Entries.Cast().ToArray(); var names = new[] { "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", diff --git a/test/AsmResolver.Tests/Utf8StringTest.cs b/test/AsmResolver.Tests/Utf8StringTest.cs index 9de95a07e..46a9d8f36 100644 --- a/test/AsmResolver.Tests/Utf8StringTest.cs +++ b/test/AsmResolver.Tests/Utf8StringTest.cs @@ -81,7 +81,7 @@ public void ByteConcat(byte[]? a, byte[]? b) { Utf8String? s1 = a; Utf8String? s2 = b; - Assert.Equal((a ?? Array.Empty()).Concat(b ?? Array.Empty()), (s1 + s2)?.GetBytes()); + Assert.Equal((a ?? Array.Empty()).Concat(b ?? Array.Empty()), (s1 + s2).GetBytes()); } [Theory] From 0f4229feedd02fd803ef849a876966f3ebd6b8ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Aug 2022 14:29:43 +0000 Subject: [PATCH 134/182] Bump BenchmarkDotNet from 0.13.1 to 0.13.2 Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.1 to 0.13.2. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.1...v0.13.2) --- updated-dependencies: - dependency-name: BenchmarkDotNet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index 5931a82d2..bd140f317 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -6,7 +6,7 @@ - + From f7c1ef63189c6b506d81939e5324422606f469d9 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 26 Aug 2022 17:00:06 +0200 Subject: [PATCH 135/182] Add CallbackCloneListener::EmptyInstance, add MemberCloner constructor overload. --- .../Cloning/CallbackCloneListener.cs | 11 ++++++- .../Cloning/MemberCloneContext.cs | 4 +-- .../Cloning/MemberCloner.cs | 30 +++++++++++++++---- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs index efa0063fd..91b195a41 100644 --- a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs +++ b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs @@ -7,7 +7,6 @@ namespace AsmResolver.DotNet.Cloning /// public class CallbackCloneListener : MemberClonerListener { - private readonly Action _callback; /// @@ -17,8 +16,18 @@ public class CallbackCloneListener : MemberClonerListener public CallbackCloneListener(Action callback) => _callback = callback; + /// + /// Gets a singleton instance of the class that performs no operation + /// on any of the cloning procedure notifications. + /// + public static CallbackCloneListener EmptyInstance + { + get; + } = new((_, _) => { }); + /// public override void OnClonedMember(IMetadataMember original, IMetadataMember cloned) => _callback(original, cloned); + } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs index 78c52c8c3..9c0a297c1 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs @@ -35,9 +35,9 @@ public ModuleDefinition Module } /// - /// Gets the object responsible for importing references into the target mdoule. + /// Gets the object responsible for importing references into the target module. /// - public ReferenceImporter Importer + public CloneContextAwareReferenceImporter Importer { get; } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index af5d27abd..31ffc6f11 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -31,21 +31,41 @@ public partial class MemberCloner /// Creates a new instance of the class. /// /// The target module to copy the members into. - public MemberCloner(ModuleDefinition targetModule) : this(targetModule, (original, cloned) => { }) { } + public MemberCloner(ModuleDefinition targetModule) + : this(targetModule, (original, cloned) => { }) + { + } + + /// + /// Creates a new instance of the class. + /// + /// The target module to copy the members into. + /// The factory for creating the reference importer + public MemberCloner(ModuleDefinition targetModule, + Func? importerFactory) + : this(targetModule, importerFactory, null) + { + } /// /// Creates a new instance of the class. /// /// The target module to copy the members into. /// The callback used in the cloner listener. - public MemberCloner(ModuleDefinition targetModule, Action callback) : this(targetModule, new CallbackCloneListener(callback)) { } + public MemberCloner(ModuleDefinition targetModule, Action callback) + : this(targetModule, new CallbackCloneListener(callback)) + { + } /// /// Creates a new instance of the class. /// /// The target module to copy the members into. /// The callback listener used in the cloner. - public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listener) : this(targetModule, null, listener) { } + public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listener) + : this(targetModule, null, listener) + { + } /// /// Creates a new instance of the class. @@ -55,11 +75,11 @@ public partial class MemberCloner /// The listener used in the cloner. public MemberCloner(ModuleDefinition targetModule, Func? importerFactory, - IMemberClonerListener clonerListener) + IMemberClonerListener? clonerListener) { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); _importerFactory = importerFactory; - _clonerListener = clonerListener; + _clonerListener = clonerListener ?? CallbackCloneListener.EmptyInstance; } /// From 9e8408bd962a1f931ab66fb75ab1449eb39a1c4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:38:18 +0000 Subject: [PATCH 136/182] Bump Microsoft.NET.Test.Sdk from 17.3.0 to 17.3.1 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.0 to 17.3.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.0...v17.3.1) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../AsmResolver.DotNet.Dynamic.Tests.csproj | 2 +- 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 +- .../AsmResolver.Symbols.Pdb.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj index 9a814bd0f..845faab14 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 48c9e7ed2..d45a4dd3f 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 e38058430..7e7716c88 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 b9f309112..82a56a966 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 efbdd2b45..e9fb1eb4f 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.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index ff08cc804..3271b7040 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -8,7 +8,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 890b3b276..7bf34457f 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,7 +8,7 @@ - + all From f0c84436684298cb431af70562070706b17a4633 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 2 Sep 2022 21:02:45 +0200 Subject: [PATCH 137/182] Extract SerializedCustomAttributeSignature to avoid unnecessary parsing of signature. --- .../Serialized/SerializedCustomAttribute.cs | 5 +- .../Signatures/CustomAttributeSignature.cs | 126 ++++++++++++------ .../SerializedCustomAttributeSignature.cs | 73 ++++++++++ 3 files changed, 157 insertions(+), 47 deletions(-) create mode 100644 src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs diff --git a/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs b/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs index 2e903be4c..193f8526a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs @@ -65,10 +65,7 @@ public SerializedCustomAttribute(ModuleReaderContext context, MetadataToken toke $"Invalid signature blob index in custom attribute {MetadataToken}."); } - return CustomAttributeSignature.FromReader( - new BlobReadContext(_context), - Constructor, - ref reader); + return CustomAttributeSignature.FromReader(new BlobReadContext(_context), Constructor, reader); } } } diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs index 15d4834fb..9a0244265 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.IO; @@ -12,55 +13,18 @@ namespace AsmResolver.DotNet.Signatures /// public class CustomAttributeSignature : ExtendableBlobSignature { - private readonly List _fixedArguments; - private readonly List _namedArguments; - private const ushort CustomAttributeSignaturePrologue = 0x0001; - /// - /// Reads a single custom attribute signature from the input stream. + /// The header value of every custom attribute signature. /// - /// The blob reader context. - /// The constructor that was called. - /// The input stream. - /// The signature. - /// Occurs when the input stream does not point to a valid signature. - public static CustomAttributeSignature? FromReader(in BlobReadContext context, ICustomAttributeType ctor, ref BinaryStreamReader reader) - { - ushort prologue = reader.ReadUInt16(); - if (prologue != CustomAttributeSignaturePrologue) - { - context.ReaderContext.BadImage("Input stream does not point to a valid custom attribute signature."); - return null; - } + protected const ushort CustomAttributeSignaturePrologue = 0x0001; - var result = new CustomAttributeSignature(); - - // 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); - } - - // 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); - } - - return result; - } + private List? _fixedArguments; + private List? _namedArguments; /// /// Creates a new empty custom attribute signature. /// public CustomAttributeSignature() - : this(Enumerable.Empty(), Enumerable.Empty()) { } @@ -84,12 +48,87 @@ public CustomAttributeSignature(IEnumerable fixedArgume /// /// Gets a collection of fixed arguments that are passed onto the constructor of the attribute. /// - public IList FixedArguments => _fixedArguments; + public IList FixedArguments + { + get + { + EnsureIsInitialized(); + return _fixedArguments; + } + } /// /// Gets a collection of values that are assigned to fields and/or members of the attribute class. /// - public IList NamedArguments => _namedArguments; + public IList NamedArguments + { + get + { + EnsureIsInitialized(); + return _namedArguments; + } + } + + /// + /// Reads a single custom attribute signature from the input stream. + /// + /// The blob reader context. + /// The constructor that was called. + /// The input stream. + /// The signature. + /// Occurs when the input stream does not point to a valid signature. + public static CustomAttributeSignature FromReader( + in BlobReadContext context, + ICustomAttributeType ctor, + in BinaryStreamReader reader) + { + var argumentTypes = ctor.Signature?.ParameterTypes ?? Array.Empty(); + return new SerializedCustomAttributeSignature(context, argumentTypes, reader); + } + + /// + /// Gets a value indicating whether the and collections + /// are initialized or not. + /// + [MemberNotNullWhen(true, nameof(_fixedArguments))] + [MemberNotNullWhen(true, nameof(_namedArguments))] + protected bool IsInitialized => _fixedArguments is not null && _namedArguments is not null; + + /// + /// Ensures that the and are initialized. + /// + [MemberNotNull(nameof(_fixedArguments))] + [MemberNotNull(nameof(_namedArguments))] + protected void EnsureIsInitialized() + { + if (IsInitialized) + return; + + lock (this) + { + if (IsInitialized) + return; + + var fixedArguments = new List(); + var namedArguments = new List(); + + Initialize(fixedArguments, namedArguments); + + _fixedArguments = fixedArguments; + _namedArguments = namedArguments; + } + } + + /// + /// Initializes the argument collections of the signature. + /// + /// The collection that will receive the fixed arguments. + /// The collection that will receive the named arguments. + protected virtual void Initialize( + IList fixedArguments, + IList namedArguments) + { + } /// public override string ToString() @@ -110,4 +149,5 @@ protected override void WriteContents(BlobSerializationContext context) NamedArguments[i].Write(context); } } + } diff --git a/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs new file mode 100644 index 000000000..e1a6bc9de --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Collections.Generic; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.IO; + +namespace AsmResolver.DotNet.Signatures +{ + /// + /// Provides a lazy initialized implementation of the class. + /// + public class SerializedCustomAttributeSignature : CustomAttributeSignature + { + private readonly BlobReadContext _context; + private readonly TypeSignature[] _fixedArgTypes; + private readonly BinaryStreamReader _reader; + + /// + /// Initializes a new lazy custom attribute signature from an input blob stream reader. + /// + /// The blob reading context the signature is situated in. + /// The types of all fixed arguments. + /// The input blob reader. + public SerializedCustomAttributeSignature( + in BlobReadContext context, + IEnumerable fixedArgTypes, + in BinaryStreamReader reader) + { + _context = context; + _fixedArgTypes = fixedArgTypes.ToArray(); + _reader = reader; + } + + /// + protected override void Initialize( + IList fixedArguments, + IList namedArguments) + { + var reader = _reader.Fork(); + + // Verify magic header. + ushort prologue = reader.ReadUInt16(); + if (prologue != CustomAttributeSignaturePrologue) + _context.ReaderContext.BadImage("Input stream does not point to a valid custom attribute signature."); + + // Read fixed arguments. + for (int i = 0; i < _fixedArgTypes.Length; i++) + fixedArguments.Add(CustomAttributeArgument.FromReader(_context, _fixedArgTypes[i], ref reader)); + + // Read named arguments. + ushort namedArgumentCount = reader.ReadUInt16(); + for (int i = 0; i < namedArgumentCount; i++) + namedArguments.Add(CustomAttributeNamedArgument.FromReader(_context, ref reader)); + } + + /// + protected override void WriteContents(BlobSerializationContext context) + { + // If the arguments are not initialized yet, it means nobody accessed the fixed or named arguments of the + // signature. In such a case, we can safely assume nothing has changed to the signature. + // + // Since custom attribute signatures reference types by their fully qualified name, they do not contain + // any metadata tokens or type indices. Thus, regardless of whether the assembly changed or not, we can + // always just use the raw blob signature without risking breaking any references into the assembly. + // This can save a lot of processing time, given the fact that many custom attribute arguments are Enum + // typed arguments, which would require an expensive type resolution to determine the size of an element. + + if (IsInitialized) + base.WriteContents(context); + else + _reader.Fork().WriteToOutput(context.Writer); + } + } +} From 535f21fb58a2518ccf115f0430e48a5a37abbfc0 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 2 Sep 2022 21:14:44 +0200 Subject: [PATCH 138/182] Add convenience constructor overloads for CAs. --- src/AsmResolver.DotNet/CustomAttribute.cs | 11 ++++++ .../Signatures/CustomAttributeSignature.cs | 8 ++++ .../CustomAttributeTest.cs | 38 +++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/src/AsmResolver.DotNet/CustomAttribute.cs b/src/AsmResolver.DotNet/CustomAttribute.cs index d3d8023f0..ddfc5d11e 100644 --- a/src/AsmResolver.DotNet/CustomAttribute.cs +++ b/src/AsmResolver.DotNet/CustomAttribute.cs @@ -25,6 +25,17 @@ protected CustomAttribute(MetadataToken token) _signature = new LazyVariable(GetSignature); } + /// + /// Creates a new custom attribute. + /// + /// The constructor of the attribute to call. + public CustomAttribute(ICustomAttributeType? constructor) + : this(new MetadataToken(TableIndex.CustomAttribute, 0)) + { + Constructor = constructor; + Signature = new CustomAttributeSignature(); + } + /// /// Creates a new custom attribute. /// diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs index 9a0244265..6e0458e07 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs @@ -28,6 +28,14 @@ public CustomAttributeSignature() { } + /// + /// Creates a new custom attribute signature with the provided fixed arguments. + /// + public CustomAttributeSignature(params CustomAttributeArgument[] fixedArguments) + : this(fixedArguments, Enumerable.Empty()) + { + } + /// /// Creates a new custom attribute signature with the provided fixed arguments. /// diff --git a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs index 3c7da128b..d358ca749 100644 --- a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs +++ b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs @@ -400,5 +400,43 @@ public void FixedInt32ArrayAsObject(bool rebuild) 1, 2, 3, 4 }, boxedArgument.Value); } + + [Fact] + public void CreateNewWithFixedArgumentsViaConstructor() + { + var module = new ModuleDefinition("Module.exe"); + + var attribute = new CustomAttribute(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "ObsoleteAttribute") + .CreateMemberReference(".ctor", MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String)), + new CustomAttributeSignature(new CustomAttributeArgument( + module.CorLibTypeFactory.String, + "My Message"))); + + Assert.NotNull(attribute.Signature); + var argument = Assert.Single(attribute.Signature.FixedArguments); + Assert.Equal("My Message", argument.Element); + } + + [Fact] + public void CreateNewWithFixedArgumentsViaProperty() + { + var module = new ModuleDefinition("Module.exe"); + + var attribute = new CustomAttribute(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "ObsoleteAttribute") + .CreateMemberReference(".ctor", MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String))); + attribute.Signature!.FixedArguments.Add(new CustomAttributeArgument( + module.CorLibTypeFactory.String, + "My Message")); + + Assert.NotNull(attribute.Signature); + var argument = Assert.Single(attribute.Signature.FixedArguments); + Assert.Equal("My Message", argument.Element); + } } } From ac96f255e4ac0a9ea6f78f09481208e772530b00 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 2 Sep 2022 21:29:10 +0200 Subject: [PATCH 139/182] Restrict scope of locked area to the final assignments only. --- .../Signatures/CustomAttributeSignature.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs index 6e0458e07..3cb564c0c 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs @@ -112,16 +112,15 @@ protected void EnsureIsInitialized() if (IsInitialized) return; + var fixedArguments = new List(); + var namedArguments = new List(); + + Initialize(fixedArguments, namedArguments); + lock (this) { if (IsInitialized) return; - - var fixedArguments = new List(); - var namedArguments = new List(); - - Initialize(fixedArguments, namedArguments); - _fixedArguments = fixedArguments; _namedArguments = namedArguments; } From 4878db1d6d19c79c2d85fb55615379e83742cafe Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 3 Sep 2022 15:11:46 +0200 Subject: [PATCH 140/182] Ensure CA tests are both testing for rebuild with and without access of the arguments. --- .../ModuleReadWriteBenchmark.cs | 35 ++- .../CustomAttributeTest.cs | 266 ++++++++++-------- .../ModuleDefinitionTest.cs | 36 +++ 3 files changed, 211 insertions(+), 126 deletions(-) diff --git a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs index 206b2cb80..cd4924fe3 100644 --- a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs +++ b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using System.Linq; using AsmResolver.DotNet; +using AsmResolver.IO; using BenchmarkDotNet.Attributes; using static AsmResolver.Benchmarks.Properties.Resources; @@ -12,15 +14,22 @@ public class ModuleReadWriteBenchmark private static readonly byte[] HelloWorldApp = HelloWorld; private static readonly byte[] CrackMeApp = Test; private static readonly byte[] ManyMethods = Utilities.DecompressDeflate(HelloWorld_ManyMethods); - private static readonly byte[] CoreLib; + private static readonly IInputFile SystemPrivateCoreLib; + private static readonly IInputFile SystemRuntime; + private static readonly IInputFile SystemPrivateXml; private readonly MemoryStream _outputStream = new(); static ModuleReadWriteBenchmark() { - var resolver = new DotNetCoreAssemblyResolver(new Version(3, 1, 0)); - string path = resolver.Resolve(KnownCorLibs.SystemPrivateCoreLib_v4_0_0_0)!.ManifestModule!.FilePath; - CoreLib = File.ReadAllBytes(path); + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + + var fs = new ByteArrayFileService(); + SystemPrivateCoreLib = fs.OpenFile(Path.Combine(runtimePath, "System.Private.CoreLib.dll")); + SystemRuntime = fs.OpenFile(Path.Combine(runtimePath, "System.Runtime.dll")); + SystemPrivateXml = fs.OpenFile(Path.Combine(runtimePath, "System.Private.Xml.dll")); } [Benchmark] @@ -63,9 +72,23 @@ public void ManyMethods_ReadWrite() } [Benchmark] - public void CoreLib_ReadWrite() + public void SystemPrivateCoreLib_ReadWrite() + { + var module = ModuleDefinition.FromFile(SystemPrivateCoreLib); + module.Write(_outputStream); + } + + [Benchmark] + public void SystemRuntimeLib_ReadWrite() + { + var module = ModuleDefinition.FromFile(SystemRuntime); + module.Write(_outputStream); + } + + [Benchmark] + public void SystemPrivateXml_ReadWrite() { - var module = ModuleDefinition.FromBytes(CoreLib); + var module = ModuleDefinition.FromFile(SystemPrivateXml); module.Write(_outputStream); } } diff --git a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs index d358ca749..1e65acde3 100644 --- a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs +++ b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs @@ -1,15 +1,12 @@ using System; using System.IO; using System.Linq; -using AsmResolver.DotNet.Builder; -using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.CustomAttributes; using AsmResolver.DotNet.TestCases.Properties; using AsmResolver.IO; using AsmResolver.PE; -using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -18,7 +15,7 @@ namespace AsmResolver.DotNet.Tests { public class CustomAttributeTest { - private readonly SignatureComparer _comparer = new SignatureComparer(); + private readonly SignatureComparer _comparer = new(); [Fact] public void ReadConstructor() @@ -27,7 +24,7 @@ public void ReadConstructor() var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); Assert.All(type.CustomAttributes, a => - Assert.Equal(nameof(TestCaseAttribute), a.Constructor.DeclaringType.Name)); + Assert.Equal(nameof(TestCaseAttribute), a.Constructor!.DeclaringType!.Name)); } [Fact] @@ -41,7 +38,7 @@ public void PersistentConstructor() var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); Assert.All(type.CustomAttributes, a => - Assert.Equal(nameof(TestCaseAttribute), a.Constructor.DeclaringType.Name)); + Assert.Equal(nameof(TestCaseAttribute), a.Constructor!.DeclaringType!.Name)); } [Fact] @@ -51,7 +48,7 @@ public void ReadParent() string filePath = typeof(CustomAttributesTestClass).Assembly.Location; var image = PEImage.FromFile(filePath); - var tablesStream = image.DotNetDirectory.Metadata.GetStream(); + var tablesStream = image.DotNetDirectory!.Metadata!.GetStream(); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasCustomAttribute); var attributeTable = tablesStream.GetTable(TableIndex.CustomAttribute); @@ -73,13 +70,19 @@ public void ReadParent() Assert.Equal(parentToken, attribute.Parent.MetadataToken); } - private static CustomAttribute GetCustomAttributeTestCase(string methodName, bool rebuild = false) + private static CustomAttribute GetCustomAttributeTestCase(string methodName, bool rebuild = false, bool access = false) { var module = ModuleDefinition.FromFile(typeof(CustomAttributesTestClass).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); var method = type.Methods.First(m => m.Name == methodName); var attribute = method.CustomAttributes - .First(c => c.Constructor.DeclaringType.Name == nameof(TestCaseAttribute)); + .First(c => c.Constructor!.DeclaringType!.Name == nameof(TestCaseAttribute)); + + if (access) + { + _ = attribute.Signature!.FixedArguments; + _ = attribute.Signature.NamedArguments; + } if (rebuild) attribute = RebuildAndLookup(attribute); @@ -89,24 +92,28 @@ private static CustomAttribute GetCustomAttributeTestCase(string methodName, boo private static CustomAttribute RebuildAndLookup(CustomAttribute attribute) { var stream = new MemoryStream(); - var method = (MethodDefinition) attribute.Parent; - method.Module.Write(stream); + var method = (MethodDefinition) attribute.Parent!; + method.Module!.Write(stream); var newModule = ModuleDefinition.FromBytes(stream.ToArray()); return newModule - .TopLevelTypes.First(t => t.FullName == method.DeclaringType.FullName) + .TopLevelTypes.First(t => t.FullName == method.DeclaringType!.FullName) .Methods.First(f => f.Name == method.Name) .CustomAttributes[0]; } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32Argument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32Argument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32Argument), rebuild); + var attribute = GetCustomAttributeTestCase( + nameof(CustomAttributesTestClass.FixedInt32Argument), + rebuild, + access); - Assert.Single(attribute.Signature.FixedArguments); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -114,12 +121,13 @@ public void FixedInt32Argument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedStringArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedStringArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedStringArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedStringArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -127,12 +135,13 @@ public void FixedStringArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedEnumArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedEnumArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedEnumArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedEnumArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -140,41 +149,44 @@ public void FixedEnumArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedNullTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedNullTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedNullTypeArgument), rebuild); - var fixedArg = Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedNullTypeArgument),rebuild, access); + var fixedArg = Assert.Single(attribute.Signature!.FixedArguments); Assert.Null(fixedArg.Element); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedTypeArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedTypeArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; Assert.Equal( - attribute.Constructor.Module.CorLibTypeFactory.String, + attribute.Constructor!.Module!.CorLibTypeFactory.String, argument.Element as TypeSignature, _comparer); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedComplexTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedComplexTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedComplexTypeArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedComplexTypeArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; - var factory = attribute.Constructor.Module.CorLibTypeFactory; + var factory = attribute.Constructor!.Module!.CorLibTypeFactory; var listRef = new TypeReference(factory.CorLibScope, "System.Collections.Generic", "KeyValuePair`2"); var instance = new GenericInstanceTypeSignature(listRef, false, @@ -185,12 +197,13 @@ public void FixedComplexTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedInt32Argument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedInt32Argument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedInt32Argument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedInt32Argument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -199,12 +212,13 @@ public void NamedInt32Argument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedStringArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedStringArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedStringArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedStringArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -213,12 +227,13 @@ public void NamedStringArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedEnumArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedEnumArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedEnumArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedEnumArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -227,16 +242,17 @@ public void NamedEnumArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedTypeArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedTypeArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var expected = new TypeReference( - attribute.Constructor.Module.CorLibTypeFactory.CorLibScope, + attribute.Constructor!.Module!.CorLibTypeFactory.CorLibScope, "System", "Int32"); var argument = attribute.Signature.NamedArguments[0]; @@ -250,22 +266,23 @@ public void IsCompilerGeneratedMember() var module = ModuleDefinition.FromFile(typeof(SingleProperty).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(SingleProperty)); var property = type.Properties.First(); - var setMethod = property.SetMethod; + var setMethod = property.SetMethod!; Assert.True(setMethod.IsCompilerGenerated()); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void GenericTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void GenericTypeArgument(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericType), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericType),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; var nestedClass = (TypeDefinition) module.LookupMember(typeof(TestGenericType<>).MetadataToken); var expected = new GenericInstanceTypeSignature(nestedClass, false, module.CorLibTypeFactory.Object); @@ -274,16 +291,17 @@ public void GenericTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void ArrayGenericTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void ArrayGenericTypeArgument(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericTypeArray), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericTypeArray),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; var nestedClass = (TypeDefinition) module.LookupMember(typeof(TestGenericType<>).MetadataToken); var expected = new SzArrayTypeSignature( new GenericInstanceTypeSignature(nestedClass, false, module.CorLibTypeFactory.Object) @@ -294,64 +312,69 @@ public void ArrayGenericTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void IntPassedOnAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void IntPassedOnAsObject(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.Int32PassedAsObject), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.Int32PassedAsObject),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); Assert.Equal(123, ((BoxedArgument) argument.Element).Value); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void TypePassedOnAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void TypePassedOnAsObject(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.TypePassedAsObject), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.TypePassedAsObject),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; Assert.IsAssignableFrom(argument.Element); Assert.Equal(module.CorLibTypeFactory.Int32, (ITypeDescriptor) ((BoxedArgument) argument.Element).Value, _comparer); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32NullArray(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32NullArray(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayNullArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayNullArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.True(argument.IsNullArray); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32EmptyArray(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32EmptyArray(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayEmptyArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayEmptyArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.False(argument.IsNullArray); Assert.Empty(argument.Elements); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32Array(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32Array(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.Equal(new[] { @@ -360,12 +383,13 @@ public void FixedInt32Array(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32ArrayNullAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32ArrayNullAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectNullArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectNullArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; @@ -373,25 +397,27 @@ public void FixedInt32ArrayNullAsObject(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32EmptyArrayAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32EmptyArrayAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectEmptyArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectEmptyArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; - Assert.Equal(new object[0], boxedArgument.Value); + Assert.Equal(Array.Empty(), boxedArgument.Value); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32ArrayAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32ArrayAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index 62b350668..44f3c016f 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -359,5 +359,41 @@ public void NewModuleShouldContainSingleReferenceToCorLib() var reference = Assert.Single(module.AssemblyReferences); Assert.Equal(KnownCorLibs.NetStandard_v2_0_0_0, reference, Comparer); } + + [Fact] + public void RewriteSystemPrivateCoreLib() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Private.CoreLib.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } + + [Fact] + public void RewriteSystemRuntime() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Runtime.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } + + [Fact] + public void RewriteSystemPrivateXml() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Private.Xml.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } } } From 1b14f385bd78b2f45053560ccd002d47978942cb Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 4 Sep 2022 18:00:42 +0200 Subject: [PATCH 141/182] Add EnC metadata stream tests. --- .../Code/Cil/CilMethodBodyTest.cs | 20 ++++++ .../ModuleDefinitionTest.cs | 22 +++++++ .../Properties/Resources.Designer.cs | 56 ++++++++++++++++ .../Properties/Resources.resx | 24 +++++++ .../HelloWorld.DoubleBlobStream.EnC.dll | Bin 0 -> 4608 bytes .../Resources/HelloWorld.DoubleBlobStream.dll | Bin 0 -> 4608 bytes .../HelloWorld.DoubleGuidStream.EnC.dll | Bin 0 -> 4608 bytes .../Resources/HelloWorld.DoubleGuidStream.dll | Bin 0 -> 4608 bytes .../HelloWorld.DoubleStringsStream.EnC.dll | Bin 0 -> 5120 bytes .../HelloWorld.DoubleStringsStream.dll | Bin 0 -> 5120 bytes ...HelloWorld.DoubleUserStringsStream.EnC.dll | Bin 0 -> 4608 bytes .../HelloWorld.DoubleUserStringsStream.dll | Bin 0 -> 4608 bytes .../TypeDefinitionTest.cs | 14 ++++ .../DotNet/Metadata/MetadataTest.cs | 62 ++++++++++++++++++ .../Properties/Resources.Designer.cs | 56 ++++++++++++++++ .../Properties/Resources.resx | 24 +++++++ .../HelloWorld.DoubleBlobStream.EnC.dll | Bin 0 -> 4608 bytes .../Resources/HelloWorld.DoubleBlobStream.dll | Bin 0 -> 4608 bytes .../HelloWorld.DoubleGuidStream.EnC.dll | Bin 0 -> 4608 bytes .../Resources/HelloWorld.DoubleGuidStream.dll | Bin 0 -> 4608 bytes .../HelloWorld.DoubleStringsStream.EnC.dll | Bin 0 -> 5120 bytes .../HelloWorld.DoubleStringsStream.dll | Bin 0 -> 5120 bytes ...HelloWorld.DoubleUserStringsStream.EnC.dll | Bin 0 -> 4608 bytes .../HelloWorld.DoubleUserStringsStream.dll | Bin 0 -> 4608 bytes 24 files changed, 278 insertions(+) create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.dll create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.EnC.dll create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.dll create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.EnC.dll create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.dll create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.EnC.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.EnC.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll diff --git a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs index f13198d60..717853367 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs @@ -597,5 +597,25 @@ public void SmallTryBlockStartingOnLargeOffsetShouldResultInFatFormat() Assert.True(handler.IsFat); } + + [Fact] + public void ReadUserStringFromNormalMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleUserStringsStream); + var instruction = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions + .First(i => i.OpCode.Code == CilCode.Ldstr); + + Assert.Equal("Hello Mars!!", instruction.Operand); + } + + [Fact] + public void ReadUserStringFromEnCMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleUserStringsStream_EnC); + var instruction = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions + .First(i => i.OpCode.Code == CilCode.Ldstr); + + Assert.Equal("Hello World!", instruction.Operand); + } } } diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index 44f3c016f..cfae099e9 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -59,6 +59,28 @@ public void ReadNameTest() Assert.Equal("HelloWorld.exe", module.Name); } + [Fact] + public void ReadMvidFromNormalMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleGuidStream); + Assert.Equal( + new Guid(new byte[] + { + 0x94, 0xe3, 0x75, 0xe2, 0x82, 0x8b, 0xac, 0x4c, 0xa3, 0x8c, 0xb3, 0x72, 0x4b, 0x81, 0xea, 0x05 + }), module.Mvid); + } + + [Fact] + public void ReadMvidFromEnCMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleGuidStream_EnC); + Assert.Equal( + new Guid(new byte[] + { + 0x8F, 0x6C, 0x77, 0x06, 0xEE, 0x44, 0x65, 0x41, 0xB0, 0xF7, 0x2D, 0xBD, 0x12, 0x7F, 0xE2, 0x1B + }), module.Mvid); + } + [Fact] public void NameIsPersistentAfterRebuild() { diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index fbf27fbcb..fffb016d9 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -171,6 +171,62 @@ public class Resources { } } + public static byte[] HelloWorld_DoubleBlobStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleBlobStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleBlobStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleBlobStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleGuidStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleGuidStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleGuidStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleGuidStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleStringsStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleStringsStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleStringsStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleStringsStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleUserStringsStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleUserStringsStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleUserStringsStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleUserStringsStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] Assembly1_Forwarder { get { object obj = ResourceManager.GetObject("Assembly1_Forwarder", resourceCulture); diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index e919e0c39..37af630d8 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -72,6 +72,30 @@ ..\Resources\HelloWorld.UnusualNestedTypeRefOrder.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.DoubleBlobStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleBlobStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleGuidStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleGuidStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleStringsStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleUserStringsStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleUserStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\Assembly1_Forwarder.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll new file mode 100644 index 0000000000000000000000000000000000000000..1d8ca99cdf2450eaa478ce2079ba1bd6db171dee GIT binary patch literal 4608 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 JfB%mR{0Guz(Fgzl literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.dll new file mode 100644 index 0000000000000000000000000000000000000000..91b35664f9dddfb5b2f2682432db34a60477c361 GIT binary patch literal 4608 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 PchBz-)bEx5KM(v1J!Og? literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.dll new file mode 100644 index 0000000000000000000000000000000000000000..205a7311a00f97a2fd119e147476ca954c31a572 GIT binary patch literal 5120 zcmeHKO>7&-6@H_?$aE#!b(1=7(yl4DRe!wtwc)suOj(p-#gZ(Gw2h`j?B#Mqt+PMu z&QcM9qIHTiDPSEGAcvlUAU(85atM$Fa2ganBssc=qAddCQpC6P;A_xB-S5rrl9c5% zDNrB<>LKUtyq|gV=FQA|GmDRJQ6~{~q29htbRAzVISv0kxC(avH-6VoZ|=Hr=(%QUmM*eKU@Pdjo`}#766V=PJL<^#mo};N>q@(?f zvb0+a)7!m7SGr^B8-w^7s5w*y20k6J-Dqk1`Ab((LFdv*Iq2N0|E+I=#v*k2+eD9X zJ4f_HGFHNhKbMKVgw`vND{fRahI$Cv)Ck4MNX~z_i>?m4dyiPPHCt zxt5Zdz^gf~Cay@=+R!c4;-?lbhwy|Q?PRb5%Wt$8i6=BYa>|bDL~Y!}dx?;ln2W`0 zbFo=*U7B$%r7n#m{-s6B@teMutFE+pzGT`_5T^9JV^#e?MUJgfQaVmNO&JzsxbE0e zC98vRvBE&H0E*6*uRtxYI|{auc=UdvU5xxgjL`$}{_cx;`lw)=>9fPfklTEGh&;h< z-k&$=9nnKa1;a`Dt=K~!g4IK7Q~>26u?6@ZF??FX7d7nE^mDp>OFSbo*ssrMm9btv z5d!=6C*X|e_Oij_Jx|xf0PrU?_UwU|p_eTZ^s(4QFT<*RfIajeV1~W}xEK8yKBD2Y zhVvSp)9{BHIy6T2lZAEC&@`g$`ouX1RABzu@yY`o)~b2TzkE7r%fEx(FDe zU7ZEexJg0&@514SH120dr}#I1_|&x>oys z61yhXN6k?v=>JMHfae`mbFIb_;yLXRlxfQzlo8aeQv*T=G>;&o;fH(&x<)XkUncpL z_x+39Hn_F3q{m0d{{I%bF^k)B$Zro%o`4c7N3BJd9h^(cVfzqtK7L+Qo zrb^SwTan0^>oP1wQbnaAz$wSKLPx4nK8TiNRBAb@6qK)&w4X}8jCev;tp-Q8iI}xY znW%7jdi3ZqO3W#y=3vsZU;WmfHlKU<`i;>K&g{SaO)_$KOyeH$E&Rc0 z12eLKNv*yWDO+)p8B@AnYH=RYiflc8ZJT|c)_)k$QZ((nfc_ygwB|K}TtD2bJl-D2 zvu7eL>%&I3&Cz#x-hai>tUn@jcBzozvr-P3Y`R9w(hA zW3gvGYrNR6%g__RiaLA~wOPw=oiqb&_vYQN zm4O|#-J`8O>{p^&@BCrn$!tCJPbhm$dX_rkId&MRU^O~o2i^%wdFJ{^*6=Lf zSw)Oa)0`d;$_%5aY`!A#>WLcLY~pm5HGJg0$?ReSiPCj!{i-k#!LviLhA6D7h#xMW z)1J|+?uW6JwCi|dH4+?@Q2w|MJ7r}#bk^}us!H`awT`7)4V0E1BvQN1H-H7XE?vWA zIGMH7yuThiDZ{K$ccyJTBH+eV%T+R&lvXly7i&_}hql>1G1PPd1SW>k6`I0g|F0J# Pe)qhEpnk9X|9RkFyL*ao literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll new file mode 100644 index 0000000000000000000000000000000000000000..dd1d0cd6a195a123034c6089aaa85664534b9bc9 GIT binary patch literal 4608 zcmeHKO>A7%6+X{@btbiwC_qCR+M98pfs*IHBnqkRjN`G1iS0O^Nutsb$(uLV;}_n~ z^xZecBOx_Zv@DWEkXUq8AzG=C$^xk@B2d*uDZ7BE09EP+ksU0uMJ$?r=f3wm&$TJ( zq7n=4_?~x`^$=a|j=AqYf>EFs&>08}9kJiQwEsNP6?E{qby5K~cj~|Pn2@mwJ^wJ#Q;Zjg zo=Mh9MDcKm=)1t)gb7FJIDPSpg_B{Ni^4Tkou z0EG2(=$Tf4_hu;F0E+A@T|#GD2tC^hI?61E=yk-!jeYB&ZdwsU_aP4kXmnofe`-f5 z#I(l2?d;`a`Hq8d2nd}lJ$2I1R%0Jm51T{g=+Nl!6KuYR)&TdR51cE}D;R$RD+7m! z9!DHsFkXs6$FD1h=l~9Ds#^fgVGcZbZnlVV5%fDupLByNMtblRNpkTENYDkq5O|ID zvkVR9jVJFX+Q!6}#wa}y$KDsqbQ-a|&MT*#aZW9fXE4sb-=?>X9y($$9H$$`4*DlV zJ+wk4aP}G30pB5pFKGB%4Lddetj6ycFB&<-^rAKy+vRK-i0yeuhBdw@c)geDsxbii z8BP6q&ug&D9vSqRv5j7XSGxdv=t007JqoxJ^BF#&;iQJM8lKhgry4pmO81imyoXHM zj+rLyrV+sJ(^U;~!lY73G~ zXny2W9oLC6(joM~LaVV5_|t2)Y;Z8;l`wct8PNNVKA=Yo+G8$Em#2bIPBt2kC%GD59Wh5>p#Q%0 z15cfmFEeRwhVWu71oZa4am&~HF?BO?QAi?&oM zmLB?i`tEznE5)mS`1H;GpWV96R*J_f$uaS;la;A~%Cx-_RBE!JDwE1vlK4VbWmt)% ziYjG*la6nNj#QOm5G}~4(oR$*s9vt|a^xr_?vzt^ z5cKr-9{KyVm;QS5*2q6j@4ovmiNY44e9OA040PzXRG^C51O3Ign##%&x%u&4;cfK|R{X2?NZAYs#|WmE~=4p0|h8Bz;e1-V5L^a4r`WBXUYMmiUe}}0GoaiWCSp)B9Kpr*p8ZAU*fs)#MyK-sQEi9PO z^HPtC&{kyY^&1=f`>OdvxDkzYKhI-6-?QzwhVh;ct#@8;2lP2Jk=FI$qnq(`t94A& zSX^~+JE-6OIn1r=nH+J0TH50}swBSq1^SF$=S;%qZ1Os3oJ_@;`MUMuye`5{056)z zO_cGLYu&UW+?dxP)NBS$eRke|W~ZGSqGLLblgOQdMGw2`A}WCswK1dZIh-(dc*iZ}nXAKj;aR@3jC(kJ%Jh0LCd9h2*_uQp z6}2|_#7|aU_;}yO^Ybk{hOT4lO2x!2oga)XL}61!Tnyc#KO=cP4{IxFH&M5>5*nOP z{=5l4WoIZfzAG=Hxd7D^TE+?x})Fv_y5SizX9aB B&B6cx literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll new file mode 100644 index 0000000000000000000000000000000000000000..ff6955484f2f59550bac444e6396da1d77e630b4 GIT binary patch literal 4608 zcmeHKO>A7%6+X{@btbiwC_qCR+M98Jv?b4fNfc7s8OLK26Weh-lSG9P$(uLV;}_n~ z^xZecBcV1_v@DWEDzWGaA*j?yWr0){5vXFp&n~T~N>!C~@2sFp2V`1gQ?^I0?T{1%z+F)qk z3P4ythn{H#cyET%4WP)r(j|1Zh0wFDprg!kh~7Y4+}O7c>ZTPzbRY6yfJW!l{-<`7 zLQHEM+|FM9FyCS1%p932`Rew@ws&>G-A^nr6FdKu$yU}fNE zM2{hke=}Z+LdUNwi0A+gYpPoS&S4Hbac;JVaS`t^>YL3}4Xj6%9Kz|E$LE7%v(*#Pp&z8QbM-8Hnw9NQO1OD0sbJ(^X>t z^fQ|J)t=X2mpwA*Q)3&w2CsGj_RxcXIr;(MPRwWcxQ3G&&T4p8!=GsA&?wzc7VsW2 zX**_`w3|i%ze{HU2k0rlAJKKdQF;~d$Mk!^Dcl(*o!0ykVAqv6OK>S^oVPCc7{^7l zmkwx2myRk{+KaW<+t_?iYr1lRPD$4dE(D=lqf$#n(le(5Uj?pA$)%hJRb4q5;mA~*k)%nbq*rxYWhctA%T`#I(G2b?`CJe_yIFt6apid#Dktza z3upc1AoQ#VLd*R^E%>|a)SIE!Ya5vgyoTdy=8|-+HI1pZJoR`nL?-NLvw#g)eyc4= zGNJjAQ*~S?%1D#!B}!)EEj$RjsD^8`7I1!Ohe5f_X{DS&-TElv>VV~8+;;fkE(Bi_i0QY<-OHiB&A4D}b4%}N zqKj~9dQIwG@ste@rQS83bA9jMJ|JU$k?(?Q==<#Ew2=>{_c~v`k%gao2?X&SCV7mVJ9n71C?oeC8*S7Lsce~wIIA(Rr%)GU9-&Mgxh?sF;mfm8f)Ta^%QSO57=@ z?jY#t?>zFCYcIWb^VZ1UPw&3_Fp0tzp?t%-rwnxHw^X2t+5`Q?xSGn$j9>6$Tku)w zN=7qAWBrv)BK@J^tsr`}K=fBWvPp}k2=7=*vFtF2^HidXfYY=DY?jW@0_a)58Q|Rh z*mdW>@w<~wj>i*P8rO%ZBwQD=6*$pRys`$~&wxB?=rvl1$O0v``F7>fu3K0z zq35L@7on}l*6TMm`1eKghj1er>wccce7LMzE6SXm;?KzxRqC4-r zJ9c8ta|JF*t}Vv%coFhK`gTymg*u);SDra^EH6}K`8CT8d^w(PNtHh_(UQddK)zQ?dKx^BvA`8+;!dTu%ZN#s(byte[] assembly, bool isEnC) + where TStream : class, IMetadataStream + { + var peImage = PEImage.FromBytes(assembly); + var metadata = peImage.DotNetDirectory!.Metadata!; + + var allStreams = metadata.Streams + .OfType() + .ToArray(); + + var dominantStream = metadata.GetStream(); + int expectedIndex = isEnC ? 0 : allStreams.Length - 1; + Assert.Equal(allStreams[expectedIndex], dominantStream); + } + + [Fact] + public void SelectLastBlobStreamInNormalMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleBlobStream, false); + } + + [Fact] + public void SelectLastGuidStreamInNormalMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleGuidStream, false); + } + + [Fact] + public void SelectLastStringsStreamInNormalMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleStringsStream, false); + } + + [Fact] + public void SelectLastUserStringsStreamInNormalMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleUserStringsStream, false); + } + + [Fact] + public void SelectFirstBlobStreamInEnCMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleBlobStream_EnC, true); + } + + [Fact] + public void SelectFirstGuidStreamInEnCMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleGuidStream_EnC, true); + } + + [Fact] + public void SelectFirstStringsStreamInEnCMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleStringsStream_EnC, true); + } + + [Fact] + public void SelectFirstUserStringsStreamInEnCMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleUserStringsStream_EnC, true); + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index df4387877..86d6f305d 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -94,6 +94,62 @@ public class Resources { } } + public static byte[] HelloWorld_DoubleBlobStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleBlobStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleBlobStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleBlobStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleGuidStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleGuidStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleGuidStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleGuidStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleStringsStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleStringsStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleStringsStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleStringsStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleUserStringsStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleUserStringsStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleUserStringsStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleUserStringsStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] HelloWorldPortablePdb { get { object obj = ResourceManager.GetObject("HelloWorldPortablePdb", resourceCulture); diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index 50e2489ac..93af0f6bb 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -39,6 +39,30 @@ ..\Resources\HelloWorld.TablesStream.ExtraData.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.DoubleBlobStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleBlobStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleGuidStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleGuidStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleStringsStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleUserStringsStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleUserStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\HelloWorld.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll new file mode 100644 index 0000000000000000000000000000000000000000..1d8ca99cdf2450eaa478ce2079ba1bd6db171dee GIT binary patch literal 4608 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 JfB%mR{0Guz(Fgzl literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.dll new file mode 100644 index 0000000000000000000000000000000000000000..91b35664f9dddfb5b2f2682432db34a60477c361 GIT binary patch literal 4608 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 PchBz-)bEx5KM(v1J!Og? literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.dll new file mode 100644 index 0000000000000000000000000000000000000000..205a7311a00f97a2fd119e147476ca954c31a572 GIT binary patch literal 5120 zcmeHKO>7&-6@H_?$aE#!b(1=7(yl4DRe!wtwc)suOj(p-#gZ(Gw2h`j?B#Mqt+PMu z&QcM9qIHTiDPSEGAcvlUAU(85atM$Fa2ganBssc=qAddCQpC6P;A_xB-S5rrl9c5% zDNrB<>LKUtyq|gV=FQA|GmDRJQ6~{~q29htbRAzVISv0kxC(avH-6VoZ|=Hr=(%QUmM*eKU@Pdjo`}#766V=PJL<^#mo};N>q@(?f zvb0+a)7!m7SGr^B8-w^7s5w*y20k6J-Dqk1`Ab((LFdv*Iq2N0|E+I=#v*k2+eD9X zJ4f_HGFHNhKbMKVgw`vND{fRahI$Cv)Ck4MNX~z_i>?m4dyiPPHCt zxt5Zdz^gf~Cay@=+R!c4;-?lbhwy|Q?PRb5%Wt$8i6=BYa>|bDL~Y!}dx?;ln2W`0 zbFo=*U7B$%r7n#m{-s6B@teMutFE+pzGT`_5T^9JV^#e?MUJgfQaVmNO&JzsxbE0e zC98vRvBE&H0E*6*uRtxYI|{auc=UdvU5xxgjL`$}{_cx;`lw)=>9fPfklTEGh&;h< z-k&$=9nnKa1;a`Dt=K~!g4IK7Q~>26u?6@ZF??FX7d7nE^mDp>OFSbo*ssrMm9btv z5d!=6C*X|e_Oij_Jx|xf0PrU?_UwU|p_eTZ^s(4QFT<*RfIajeV1~W}xEK8yKBD2Y zhVvSp)9{BHIy6T2lZAEC&@`g$`ouX1RABzu@yY`o)~b2TzkE7r%fEx(FDe zU7ZEexJg0&@514SH120dr}#I1_|&x>oys z61yhXN6k?v=>JMHfae`mbFIb_;yLXRlxfQzlo8aeQv*T=G>;&o;fH(&x<)XkUncpL z_x+39Hn_F3q{m0d{{I%bF^k)B$Zro%o`4c7N3BJd9h^(cVfzqtK7L+Qo zrb^SwTan0^>oP1wQbnaAz$wSKLPx4nK8TiNRBAb@6qK)&w4X}8jCev;tp-Q8iI}xY znW%7jdi3ZqO3W#y=3vsZU;WmfHlKU<`i;>K&g{SaO)_$KOyeH$E&Rc0 z12eLKNv*yWDO+)p8B@AnYH=RYiflc8ZJT|c)_)k$QZ((nfc_ygwB|K}TtD2bJl-D2 zvu7eL>%&I3&Cz#x-hai>tUn@jcBzozvr-P3Y`R9w(hA zW3gvGYrNR6%g__RiaLA~wOPw=oiqb&_vYQN zm4O|#-J`8O>{p^&@BCrn$!tCJPbhm$dX_rkId&MRU^O~o2i^%wdFJ{^*6=Lf zSw)Oa)0`d;$_%5aY`!A#>WLcLY~pm5HGJg0$?ReSiPCj!{i-k#!LviLhA6D7h#xMW z)1J|+?uW6JwCi|dH4+?@Q2w|MJ7r}#bk^}us!H`awT`7)4V0E1BvQN1H-H7XE?vWA zIGMH7yuThiDZ{K$ccyJTBH+eV%T+R&lvXly7i&_}hql>1G1PPd1SW>k6`I0g|F0J# Pe)qhEpnk9X|9RkFyL*ao literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll new file mode 100644 index 0000000000000000000000000000000000000000..dd1d0cd6a195a123034c6089aaa85664534b9bc9 GIT binary patch literal 4608 zcmeHKO>A7%6+X{@btbiwC_qCR+M98pfs*IHBnqkRjN`G1iS0O^Nutsb$(uLV;}_n~ z^xZecBOx_Zv@DWEkXUq8AzG=C$^xk@B2d*uDZ7BE09EP+ksU0uMJ$?r=f3wm&$TJ( zq7n=4_?~x`^$=a|j=AqYf>EFs&>08}9kJiQwEsNP6?E{qby5K~cj~|Pn2@mwJ^wJ#Q;Zjg zo=Mh9MDcKm=)1t)gb7FJIDPSpg_B{Ni^4Tkou z0EG2(=$Tf4_hu;F0E+A@T|#GD2tC^hI?61E=yk-!jeYB&ZdwsU_aP4kXmnofe`-f5 z#I(l2?d;`a`Hq8d2nd}lJ$2I1R%0Jm51T{g=+Nl!6KuYR)&TdR51cE}D;R$RD+7m! z9!DHsFkXs6$FD1h=l~9Ds#^fgVGcZbZnlVV5%fDupLByNMtblRNpkTENYDkq5O|ID zvkVR9jVJFX+Q!6}#wa}y$KDsqbQ-a|&MT*#aZW9fXE4sb-=?>X9y($$9H$$`4*DlV zJ+wk4aP}G30pB5pFKGB%4Lddetj6ycFB&<-^rAKy+vRK-i0yeuhBdw@c)geDsxbii z8BP6q&ug&D9vSqRv5j7XSGxdv=t007JqoxJ^BF#&;iQJM8lKhgry4pmO81imyoXHM zj+rLyrV+sJ(^U;~!lY73G~ zXny2W9oLC6(joM~LaVV5_|t2)Y;Z8;l`wct8PNNVKA=Yo+G8$Em#2bIPBt2kC%GD59Wh5>p#Q%0 z15cfmFEeRwhVWu71oZa4am&~HF?BO?QAi?&oM zmLB?i`tEznE5)mS`1H;GpWV96R*J_f$uaS;la;A~%Cx-_RBE!JDwE1vlK4VbWmt)% ziYjG*la6nNj#QOm5G}~4(oR$*s9vt|a^xr_?vzt^ z5cKr-9{KyVm;QS5*2q6j@4ovmiNY44e9OA040PzXRG^C51O3Ign##%&x%u&4;cfK|R{X2?NZAYs#|WmE~=4p0|h8Bz;e1-V5L^a4r`WBXUYMmiUe}}0GoaiWCSp)B9Kpr*p8ZAU*fs)#MyK-sQEi9PO z^HPtC&{kyY^&1=f`>OdvxDkzYKhI-6-?QzwhVh;ct#@8;2lP2Jk=FI$qnq(`t94A& zSX^~+JE-6OIn1r=nH+J0TH50}swBSq1^SF$=S;%qZ1Os3oJ_@;`MUMuye`5{056)z zO_cGLYu&UW+?dxP)NBS$eRke|W~ZGSqGLLblgOQdMGw2`A}WCswK1dZIh-(dc*iZ}nXAKj;aR@3jC(kJ%Jh0LCd9h2*_uQp z6}2|_#7|aU_;}yO^Ybk{hOT4lO2x!2oga)XL}61!Tnyc#KO=cP4{IxFH&M5>5*nOP z{=5l4WoIZfzAG=Hxd7D^TE+?x})Fv_y5SizX9aB B&B6cx literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll new file mode 100644 index 0000000000000000000000000000000000000000..ff6955484f2f59550bac444e6396da1d77e630b4 GIT binary patch literal 4608 zcmeHKO>A7%6+X{@btbiwC_qCR+M98Jv?b4fNfc7s8OLK26Weh-lSG9P$(uLV;}_n~ z^xZecBcV1_v@DWEDzWGaA*j?yWr0){5vXFp&n~T~N>!C~@2sFp2V`1gQ?^I0?T{1%z+F)qk z3P4ythn{H#cyET%4WP)r(j|1Zh0wFDprg!kh~7Y4+}O7c>ZTPzbRY6yfJW!l{-<`7 zLQHEM+|FM9FyCS1%p932`Rew@ws&>G-A^nr6FdKu$yU}fNE zM2{hke=}Z+LdUNwi0A+gYpPoS&S4Hbac;JVaS`t^>YL3}4Xj6%9Kz|E$LE7%v(*#Pp&z8QbM-8Hnw9NQO1OD0sbJ(^X>t z^fQ|J)t=X2mpwA*Q)3&w2CsGj_RxcXIr;(MPRwWcxQ3G&&T4p8!=GsA&?wzc7VsW2 zX**_`w3|i%ze{HU2k0rlAJKKdQF;~d$Mk!^Dcl(*o!0ykVAqv6OK>S^oVPCc7{^7l zmkwx2myRk{+KaW<+t_?iYr1lRPD$4dE(D=lqf$#n(le(5Uj?pA$)%hJRb4q5;mA~*k)%nbq*rxYWhctA%T`#I(G2b?`CJe_yIFt6apid#Dktza z3upc1AoQ#VLd*R^E%>|a)SIE!Ya5vgyoTdy=8|-+HI1pZJoR`nL?-NLvw#g)eyc4= zGNJjAQ*~S?%1D#!B}!)EEj$RjsD^8`7I1!Ohe5f_X{DS&-TElv>VV~8+;;fkE(Bi_i0QY<-OHiB&A4D}b4%}N zqKj~9dQIwG@ste@rQS83bA9jMJ|JU$k?(?Q==<#Ew2=>{_c~v`k%gao2?X&SCV7mVJ9n71C?oeC8*S7Lsce~wIIA(Rr%)GU9-&Mgxh?sF;mfm8f)Ta^%QSO57=@ z?jY#t?>zFCYcIWb^VZ1UPw&3_Fp0tzp?t%-rwnxHw^X2t+5`Q?xSGn$j9>6$Tku)w zN=7qAWBrv)BK@J^tsr`}K=fBWvPp}k2=7=*vFtF2^HidXfYY=DY?jW@0_a)58Q|Rh z*mdW>@w<~wj>i*P8rO%ZBwQD=6*$pRys`$~&wxB?=rvl1$O0v``F7>fu3K0z zq35L@7on}l*6TMm`1eKghj1er>wccce7LMzE6SXm;?KzxRqC4-r zJ9c8ta|JF*t}Vv%coFhK`gTymg*u);SDra^EH6}K`8CT8d^w(PNtHh_(UQddK)zQ?dKx^BvA`8+;!dTu%ZN#s Date: Sun, 4 Sep 2022 19:49:04 +0200 Subject: [PATCH 142/182] Account for ENC metadata loading order in Metadata.TryGetStream. --- .../DotNet/Metadata/IMetadata.cs | 8 +++ .../DotNet/Metadata/Metadata.cs | 72 +++++++++++++++---- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs index 5711bf1a3..841ae7fc1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs @@ -60,6 +60,14 @@ ushort Flags set; } + /// + /// Gets a value indicating whether the metadata directory is loaded as Edit-and-Continue metadata. + /// + public bool IsEnCMetadata + { + get; + } + /// /// Gets a collection of metadata streams that are defined in the metadata header. /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs index 9bc2e415a..e89caebd0 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs @@ -1,9 +1,11 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Tables; namespace AsmResolver.PE.DotNet.Metadata { @@ -49,6 +51,22 @@ public ushort Flags set; } + /// + public bool IsEnCMetadata + { + get + { + var streams = Streams; + for (int i = 0; i < streams.Count; i++) + { + if (streams[i].Name == TablesStream.EncStreamName) + return true; + } + + return false; + } + } + /// public IList Streams { @@ -216,33 +234,57 @@ public TStream GetStream() /// public bool TryGetStream(string name, [NotNullWhen(true)] out IMetadataStream? stream) { - var streams = Streams; + bool heapRequested = name is not (TablesStream.CompressedStreamName + or TablesStream.EncStreamName + or TablesStream.UncompressedStreamName); + + return TryFindStream((c, s) => c.Name == s as string, name, heapRequested, out stream); + } + + /// + public bool TryGetStream([NotNullWhen(true)] out TStream? stream) + where TStream : class, IMetadataStream + { + bool heapRequested = !typeof(TablesStream).IsAssignableFrom(typeof(TStream)); - for (int i = 0; i < streams.Count; i++) + if (TryFindStream((c, _) => c is TStream, null, heapRequested, out var candidate)) { - if (streams[i].Name == name) - { - stream = streams[i]; - return true; - } + stream = (TStream) candidate; + return true; } stream = null; return false; } - /// - public bool TryGetStream([NotNullWhen(true)] out TStream? stream) - where TStream : class, IMetadataStream + private bool TryFindStream( + Func condition, + object? state, + bool heapRequested, + [NotNullWhen(true)] out IMetadataStream? stream) { var streams = Streams; - - for (int i = 0; i < streams.Count; i++) + bool reverseOrder = heapRequested && !IsEnCMetadata; + if (reverseOrder) + { + for (int i = streams.Count - 1; i >= 0; i--) + { + if (condition(streams[i], state)) + { + stream = streams[i]; + return true; + } + } + } + else { - if (streams[i] is TStream s) + for (int i = 0; i < streams.Count; i++) { - stream = s; - return true; + if (condition(streams[i], state)) + { + stream = streams[i]; + return true; + } } } From 77e05878afde51f01b037ad11bd3b3344290d435 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 4 Sep 2022 19:49:38 +0200 Subject: [PATCH 143/182] Account for ENC metadata loading order in entire AsmResolver.DotNet. --- .../CachedSerializedMemberFactory.cs | 2 +- .../Serialized/ModuleReaderContext.cs | 84 +++++++++++++++++++ .../SerializedAssemblyDefinition.cs | 36 +++----- .../Serialized/SerializedAssemblyReference.cs | 21 +---- .../Serialized/SerializedConstant.cs | 2 +- .../Serialized/SerializedCustomAttribute.cs | 5 +- .../Serialized/SerializedEventDefinition.cs | 10 +-- .../Serialized/SerializedExportedType.cs | 17 +--- .../Serialized/SerializedFieldDefinition.cs | 15 +--- .../Serialized/SerializedFileReference.cs | 14 +--- .../Serialized/SerializedGenericParameter.cs | 10 +-- .../SerializedGenericParameterConstraint.cs | 3 +- .../Serialized/SerializedImplementationMap.cs | 7 +- .../SerializedInterfaceImplementation.cs | 3 +- .../Serialized/SerializedManifestResource.cs | 11 +-- .../Serialized/SerializedMemberReference.cs | 12 +-- .../Serialized/SerializedMethodDefinition.cs | 9 +- .../Serialized/SerializedMethodSemantics.cs | 3 +- .../SerializedMethodSpecification.cs | 5 +- .../SerializedModuleDefinition.Metadata.cs | 35 ++++---- .../Serialized/SerializedModuleDefinition.cs | 74 ++++------------ .../Serialized/SerializedModuleReference.cs | 7 +- .../SerializedParameterDefinition.cs | 7 +- .../SerializedPropertyDefinition.cs | 9 +- .../SerializedSecurityDeclaration.cs | 2 +- .../SerializedStandAloneSignature.cs | 2 +- .../Serialized/SerializedTypeDefinition.cs | 19 +---- .../Serialized/SerializedTypeReference.cs | 16 +--- .../Serialized/SerializedTypeSpecification.cs | 2 +- 29 files changed, 174 insertions(+), 268 deletions(-) diff --git a/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs b/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs index c21fc9e91..909126c46 100644 --- a/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs +++ b/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs @@ -39,7 +39,7 @@ internal class CachedSerializedMemberFactory internal CachedSerializedMemberFactory(ModuleReaderContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); - _tablesStream = _context.Image.DotNetDirectory!.Metadata!.GetStream(); + _tablesStream = _context.TablesStream; } internal bool TryLookupMember(MetadataToken token, [NotNullWhen(true)] out IMetadataMember? member) diff --git a/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs b/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs index 8329d537c..7648fc42d 100644 --- a/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs +++ b/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs @@ -1,6 +1,11 @@ using System; using AsmResolver.PE; using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Blob; +using AsmResolver.PE.DotNet.Metadata.Guid; +using AsmResolver.PE.DotNet.Metadata.Strings; +using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.UserStrings; namespace AsmResolver.DotNet.Serialized { @@ -21,6 +26,45 @@ public ModuleReaderContext(IPEImage image, SerializedModuleDefinition parentModu Image = image ?? throw new ArgumentNullException(nameof(image)); ParentModule = parentModule ?? throw new ArgumentNullException(nameof(parentModule)); Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); + + // Both CLR and CoreCLR implement a slightly different loading procedure for EnC metadata. + // While the difference is very subtle, it has a slight effect on which streams are selected + // when multiple streams with the same name are present in the metadata directory. This only + // really happens in post-processed binaries (e.g., obfuscated binaries). Any normal .NET + // compiler only emits one stream for each stream type. + // + // For normal metadata (i.e., metadata with a #~ stream), every stream is loaded. This means that + // if a stream has the same name as a previously loaded one, it will override the contents of the previous. + // On the other hand, EnC metadata (i.e., metadata with a #- stream) looks up the first occurrence + // of the stream of the provided name. The exception for this is the tables stream itself, for which both + // the CLR and CoreCLR seem to always result in a file corruption error when there are multiple table streams. + bool isEnCMetadata = Metadata.IsEnCMetadata; + + for (int i = 0; i < Metadata.Streams.Count; i++) + { + switch (Metadata.Streams[i]) + { + case TablesStream tablesStream when TablesStream is null: + TablesStream = tablesStream; + break; + case BlobStream blobStream when BlobStream is null || !isEnCMetadata: + BlobStream = blobStream; + break; + case GuidStream guidStream when GuidStream is null || !isEnCMetadata: + GuidStream = guidStream; + break; + case StringsStream stringsStream when StringsStream is null || !isEnCMetadata: + StringsStream = stringsStream; + break; + case UserStringsStream userStringsStream when UserStringsStream is null || !isEnCMetadata: + UserStringsStream = userStringsStream; + break; + } + } + + // There should at least be a tables stream. + if (TablesStream is null) + throw new ArgumentException("Metadata directory does not contain a tables stream."); } /// @@ -44,6 +88,46 @@ public SerializedModuleDefinition ParentModule /// public IMetadata Metadata => Image.DotNetDirectory!.Metadata!; + /// + /// Gets the main tables stream in the metadata directory. + /// + public TablesStream TablesStream + { + get; + } + + /// + /// Gets the main blob stream in the metadata directory. + /// + public BlobStream? BlobStream + { + get; + } + + /// + /// Gets the main GUID stream in the metadata directory. + /// + public GuidStream? GuidStream + { + get; + } + + /// + /// Gets the main strings stream in the metadata directory. + /// + public StringsStream? StringsStream + { + get; + } + + /// + /// Gets the main user-strings stream in the metadata directory. + /// + public UserStringsStream? UserStringsStream + { + get; + } + /// /// Gets the reader parameters. /// diff --git a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs index c5b4e557f..3af420240 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs @@ -47,28 +47,13 @@ public class SerializedAssemblyDefinition : AssemblyDefinition } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// - protected override Utf8String? GetCulture() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Culture) - : null; - } + protected override Utf8String? GetCulture() => _context.StringsStream?.GetStringByIndex(_row.Culture); /// - protected override byte[]? GetPublicKey() - { - return _context.Metadata.TryGetStream(out var blobStream) - ? blobStream.GetBlobByIndex(_row.PublicKey) - : null; - } + protected override byte[]? GetPublicKey() => _context.BlobStream?.GetBlobByIndex(_row.PublicKey); /// protected override IList GetModules() @@ -82,13 +67,15 @@ protected override IList GetModules() var moduleResolver = _context.Parameters.ModuleResolver; if (moduleResolver is not null) { - var metadata = _context.Image.DotNetDirectory!.Metadata!; - var tablesStream = metadata.GetStream(); - var stringsStream = metadata.GetStream(); + var tablesStream = _context.TablesStream; + var stringsStream = _context.StringsStream; + if (stringsStream is null) + return result; var filesTable = tablesStream.GetTable(TableIndex.File); - foreach (var fileRow in filesTable) + for (int i = 0; i < filesTable.Count; i++) { + var fileRow = filesTable[i]; if (fileRow.Attributes == FileAttributes.ContainsMetadata) { string? name = stringsStream.GetStringByIndex(fileRow.Name); @@ -115,9 +102,8 @@ public override bool TryGetTargetFramework(out DotNetRuntimeInfo info) // We need to override this to be able to detect the runtime without lazily resolving all kinds of members. // Get relevant streams. - var metadata = _manifestModule.DotNetDirectory.Metadata!; - var tablesStream = metadata.GetStream(); - if (!metadata.TryGetStream(out var stringsStream)) + var tablesStream = _context.TablesStream; + if (_context.StringsStream is not { } stringsStream) { info = default; return false; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyReference.cs index 372edc8a9..4ee1756ad 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyReference.cs @@ -34,28 +34,13 @@ public SerializedAssemblyReference(ModuleReaderContext context, MetadataToken to } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// - protected override Utf8String? GetCulture() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Culture) - : null; - } + protected override Utf8String? GetCulture() => _context.StringsStream?.GetStringByIndex(_row.Culture); /// - protected override byte[]? GetPublicKeyOrToken() - { - return _context.Metadata.TryGetStream(out var blobStream) - ? blobStream.GetBlobByIndex(_row.PublicKeyOrToken) - : null; - } + protected override byte[]? GetPublicKeyOrToken() => _context.BlobStream?.GetBlobByIndex(_row.PublicKeyOrToken); /// protected override IList GetCustomAttributes() => diff --git a/src/AsmResolver.DotNet/Serialized/SerializedConstant.cs b/src/AsmResolver.DotNet/Serialized/SerializedConstant.cs index a245d2cd5..6b0b4024a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedConstant.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedConstant.cs @@ -45,7 +45,7 @@ public class SerializedConstant : Constant /// protected override DataBlobSignature? GetValue() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Value, out var reader)) { // Don't report error. null constants are allowed (e.g. null strings). diff --git a/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs b/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs index 193f8526a..9809a0516 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs @@ -42,8 +42,7 @@ public SerializedCustomAttribute(ModuleReaderContext context, MetadataToken toke /// protected override ICustomAttributeType? GetConstructor() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.CustomAttributeType) .DecodeIndex(_row.Type); @@ -58,7 +57,7 @@ public SerializedCustomAttribute(ModuleReaderContext context, MetadataToken toke if (Constructor is null) return null; - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Value, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs index 4b4e83c4a..086de5b61 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs @@ -34,18 +34,12 @@ public SerializedEventDefinition(ModuleReaderContext context, MetadataToken toke } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override ITypeDefOrRef? GetEventType() { - var eventTypeToken = _context.Metadata - .GetStream() + var eventTypeToken = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeDefOrRef) .DecodeIndex(_row.EventType); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedExportedType.cs b/src/AsmResolver.DotNet/Serialized/SerializedExportedType.cs index 5a2ff8938..9c5ed48b9 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedExportedType.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedExportedType.cs @@ -34,26 +34,15 @@ public SerializedExportedType(ModuleReaderContext context, MetadataToken token, } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// - protected override Utf8String? GetNamespace() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Namespace) - : null; - } + protected override Utf8String? GetNamespace() => _context.StringsStream?.GetStringByIndex(_row.Namespace); /// protected override IImplementation? GetImplementation() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.Implementation) .DecodeIndex(_row.Implementation); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs index d9012ea83..4ee4fa366 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs @@ -35,17 +35,12 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override FieldSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( @@ -90,8 +85,7 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke var module = _context.ParentModule; uint rid = module.GetFieldRvaRid(MetadataToken); - bool result = _context.Metadata - .GetStream() + bool result = _context.TablesStream .GetTable() .TryGetByRid(rid, out var fieldRvaRow); @@ -104,8 +98,7 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke protected override int? GetFieldOffset() { uint rid = _context.ParentModule.GetFieldLayoutRid(MetadataToken); - bool result = _context.Metadata - .GetStream() + bool result = _context.TablesStream .GetTable() .TryGetByRid(rid, out var fieldLayoutRow); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedFileReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedFileReference.cs index 11e530681..71cb7b69e 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedFileReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedFileReference.cs @@ -35,20 +35,10 @@ public class SerializedFileReference : FileReference } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// - protected override byte[]? GetHashValue() - { - return _context.Metadata.TryGetStream(out var blobStream) - ? blobStream.GetBlobByIndex(_row.HashValue) - : null; - } + protected override byte[]? GetHashValue() => _context.BlobStream?.GetBlobByIndex(_row.HashValue); /// protected override IList GetCustomAttributes() => diff --git a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs index 3d876c1d5..592a6ef1a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs @@ -34,18 +34,12 @@ public SerializedGenericParameter(ModuleReaderContext context, MetadataToken tok } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override IHasGenericParameters? GetOwner() { - var ownerToken = _context.Metadata - .GetStream() + var ownerToken = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeOrMethodDef) .DecodeIndex(_row.Owner); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameterConstraint.cs b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameterConstraint.cs index 8a1fa8066..465669f09 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameterConstraint.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameterConstraint.cs @@ -43,8 +43,7 @@ public class SerializedGenericParameterConstraint : GenericParameterConstraint /// protected override ITypeDefOrRef? GetConstraint() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeDefOrRef) .DecodeIndex(_row.Constraint); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedImplementationMap.cs b/src/AsmResolver.DotNet/Serialized/SerializedImplementationMap.cs index b0fa9d372..26858a826 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedImplementationMap.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedImplementationMap.cs @@ -43,12 +43,7 @@ public class SerializedImplementationMap : ImplementationMap } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.ImportName) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.ImportName); /// protected override ModuleReference? GetScope() diff --git a/src/AsmResolver.DotNet/Serialized/SerializedInterfaceImplementation.cs b/src/AsmResolver.DotNet/Serialized/SerializedInterfaceImplementation.cs index a49ddd6f8..d2c216f34 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedInterfaceImplementation.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedInterfaceImplementation.cs @@ -45,8 +45,7 @@ public class SerializedInterfaceImplementation : InterfaceImplementation /// protected override ITypeDefOrRef? GetInterface() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeDefOrRef) .DecodeIndex(_row.Interface); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedManifestResource.cs b/src/AsmResolver.DotNet/Serialized/SerializedManifestResource.cs index 68a75abc8..2a81b2a1a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedManifestResource.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedManifestResource.cs @@ -34,12 +34,7 @@ public SerializedManifestResource(ModuleReaderContext context, MetadataToken tok } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override IImplementation? GetImplementation() @@ -47,9 +42,7 @@ public SerializedManifestResource(ModuleReaderContext context, MetadataToken tok if (_row.Implementation == 0) return null; - var encoder = _context.Metadata - .GetStream() - .GetIndexEncoder(CodedIndex.Implementation); + var encoder = _context.TablesStream.GetIndexEncoder(CodedIndex.Implementation); var token = encoder.DecodeIndex(_row.Implementation); return _context.ParentModule.TryLookupMember(token, out var member) diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMemberReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedMemberReference.cs index e8ce03db4..1a867ef11 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMemberReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMemberReference.cs @@ -37,8 +37,7 @@ public class SerializedMemberReference : MemberReference /// protected override IMemberRefParent? GetParent() { - var encoder = _context.Metadata - .GetStream() + var encoder = _context.TablesStream .GetIndexEncoder(CodedIndex.MemberRefParent); var parentToken = encoder.DecodeIndex(_row.Parent); @@ -49,17 +48,12 @@ public class SerializedMemberReference : MemberReference } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override CallingConventionSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs index c35f28b9e..0d09b8e0a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs @@ -40,17 +40,12 @@ public SerializedMethodDefinition(ModuleReaderContext context, MetadataToken tok } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override MethodSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodSemantics.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodSemantics.cs index fec338689..650190976 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodSemantics.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodSemantics.cs @@ -42,8 +42,7 @@ public SerializedMethodSemantics(ModuleReaderContext context, MetadataToken toke /// protected override IHasSemantics? GetAssociation() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.HasSemantics) .DecodeIndex(_row.Association); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodSpecification.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodSpecification.cs index cff8d92d4..929366651 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodSpecification.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodSpecification.cs @@ -32,8 +32,7 @@ public SerializedMethodSpecification(ModuleReaderContext context, MetadataToken /// protected override IMethodDefOrRef? GetMethod() { - var methodToken = _context.Metadata - .GetStream() + var methodToken = _context.TablesStream .GetIndexEncoder(CodedIndex.MethodDefOrRef) .DecodeIndex(_row.Method); @@ -46,7 +45,7 @@ public SerializedMethodSpecification(ModuleReaderContext context, MetadataToken /// protected override GenericInstanceMethodSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Instantiation, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs index e81e548aa..a8f34c45f 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs @@ -49,7 +49,7 @@ private void EnsureTypeDefinitionTreeInitialized() private OneToManyRelation InitializeTypeDefinitionTree() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var nestedClassTable = tablesStream.GetTable(TableIndex.NestedClass); var typeDefTree = new OneToManyRelation(); @@ -103,7 +103,7 @@ private void EnsureMethodSemanticsInitialized() [MemberNotNull(nameof(_semanticMethods))] private void InitializeMethodSemantics() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var semanticsTable = tablesStream.GetTable(TableIndex.MethodSemantics); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasSemantics); @@ -151,7 +151,7 @@ private void EnsureConstantsInitialized() private OneToOneRelation GetConstants() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var constantTable = tablesStream.GetTable(TableIndex.Constant); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasConstant); @@ -195,7 +195,7 @@ private void EnsureCustomAttributesInitialized() private OneToManyRelation InitializeCustomAttributes() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var attributeTable = tablesStream.GetTable(TableIndex.CustomAttribute); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasCustomAttribute); @@ -249,7 +249,7 @@ private void EnsureSecurityDeclarationsInitialized() private OneToManyRelation InitializeSecurityDeclarations() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var declarationTable = tablesStream.GetTable(TableIndex.DeclSecurity); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasDeclSecurity); @@ -294,7 +294,7 @@ private void EnsureGenericParametersInitialized() private OneToManyRelation InitializeGenericParameters() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var parameterTable = tablesStream.GetTable(TableIndex.GenericParam); var encoder = tablesStream.GetIndexEncoder(CodedIndex.TypeOrMethodDef); @@ -330,7 +330,7 @@ private void EnsureGenericParameterConstrainsInitialized() private OneToManyRelation InitializeGenericParameterConstraints() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var constraintTable = tablesStream.GetTable(TableIndex.GenericParamConstraint); var constraints = new OneToManyRelation(constraintTable.Count); @@ -365,7 +365,7 @@ private void EnsureInterfacesInitialized() private OneToManyRelation InitializeInterfaces() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var interfaceImplTable = tablesStream.GetTable(TableIndex.InterfaceImpl); var interfaces = new OneToManyRelation(interfaceImplTable.Count); @@ -400,7 +400,7 @@ private void EnsureMethodImplementationsInitialized() private OneToManyRelation InitializeMethodImplementations() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var methodImplTable = tablesStream.GetTable(TableIndex.MethodImpl); var methodImplementations = new OneToManyRelation(methodImplTable.Count); @@ -429,7 +429,7 @@ private void EnsureClassLayoutsInitialized() private OneToOneRelation InitializeClassLayouts() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var layoutTable = tablesStream.GetTable(TableIndex.ClassLayout); var layouts = new OneToOneRelation(layoutTable.Count); @@ -458,7 +458,7 @@ private void EnsureImplementationMapsInitialized() private OneToOneRelation InitializeImplementationMaps() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var mapTable = tablesStream.GetTable(TableIndex.ImplMap); var encoder = tablesStream.GetIndexEncoder(CodedIndex.TypeOrMethodDef); @@ -494,7 +494,7 @@ private void EnsureFieldRvasInitialized() private OneToOneRelation InitializeFieldRvas() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var rvaTable = tablesStream.GetTable(TableIndex.FieldRva); var rvas = new OneToOneRelation(rvaTable.Count); @@ -523,7 +523,7 @@ private void EnsureFieldMarshalsInitialized() private OneToOneRelation InitializeFieldMarshals() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var marshalTable = tablesStream.GetTable(TableIndex.FieldMarshal); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasFieldMarshal); @@ -546,12 +546,9 @@ internal uint GetFieldMarshalRid(MetadataToken fieldToken) internal MarshalDescriptor? GetFieldMarshal(MetadataToken ownerToken) { - var metadata = DotNetDirectory.Metadata!; - var table = metadata - .GetStream() - .GetTable(TableIndex.FieldMarshal); + var table = ReaderContext.TablesStream.GetTable(TableIndex.FieldMarshal); - if (!metadata.TryGetStream(out var blobStream) + if (ReaderContext.BlobStream is not { } blobStream || !table.TryGetByRid(GetFieldMarshalRid(ownerToken), out var row) || !blobStream.TryGetBlobReaderByIndex(row.NativeType, out var reader)) { @@ -570,7 +567,7 @@ private void EnsureFieldLayoutsInitialized() private OneToOneRelation InitializeFieldLayouts() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var layoutTable = tablesStream.GetTable(TableIndex.FieldLayout); var fieldLayouts = new OneToOneRelation(layoutTable.Count); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index 5ed4f0d4a..bacbaffea 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -119,7 +119,7 @@ public ModuleReaderContext ReaderContext /// public override bool TryLookupString(MetadataToken token, [NotNullWhen(true)] out string? value) { - if (!ReaderContext.Metadata.TryGetStream(out var userStringsStream)) + if (ReaderContext.UserStringsStream is not { } userStringsStream) { value = null; return false; @@ -131,14 +131,12 @@ public override bool TryLookupString(MetadataToken token, [NotNullWhen(true)] ou /// public override IndexEncoder GetIndexEncoder(CodedIndex codedIndex) => - ReaderContext.Metadata.GetStream().GetIndexEncoder(codedIndex); + ReaderContext.TablesStream.GetIndexEncoder(codedIndex); /// public override IEnumerable GetImportedTypeReferences() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.TypeRef); + var table = ReaderContext.TablesStream.GetTable(TableIndex.TypeRef); for (uint rid = 1; rid <= table.Count; rid++) { @@ -153,9 +151,7 @@ public override IEnumerable GetImportedTypeReferences() /// public override IEnumerable GetImportedMemberReferences() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.MemberRef); + var table = ReaderContext.TablesStream.GetTable(TableIndex.MemberRef); for (uint rid = 1; rid <= table.Count; rid++) { @@ -168,50 +164,24 @@ public override IEnumerable GetImportedMemberReferences() } /// - protected override Utf8String? GetName() - { - return ReaderContext.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => ReaderContext.StringsStream?.GetStringByIndex(_row.Name); /// - protected override Guid GetMvid() - { - return ReaderContext.Metadata.TryGetStream(out var guidStream) - ? guidStream.GetGuidByIndex(_row.Mvid) - : Guid.Empty; - } + protected override Guid GetMvid() => ReaderContext.GuidStream?.GetGuidByIndex(_row.Mvid) ?? Guid.Empty; /// - protected override Guid GetEncId() - { - return ReaderContext.Metadata.TryGetStream(out var guidStream) - ? guidStream.GetGuidByIndex(_row.EncId) - : Guid.Empty; - } + protected override Guid GetEncId() => ReaderContext.GuidStream?.GetGuidByIndex(_row.EncId) ?? Guid.Empty; /// - protected override Guid GetEncBaseId() - { - return ReaderContext.Metadata.TryGetStream(out var guidStream) - ? guidStream.GetGuidByIndex(_row.EncBaseId) - : Guid.Empty; - } + protected override Guid GetEncBaseId() => ReaderContext.GuidStream?.GetGuidByIndex(_row.EncBaseId) ?? Guid.Empty; /// protected override IList GetTopLevelTypes() { EnsureTypeDefinitionTreeInitialized(); - var typeDefTable = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.TypeDef); - - int nestedTypeCount = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.NestedClass) - .Count; + var typeDefTable = ReaderContext.TablesStream.GetTable(TableIndex.TypeDef); + int nestedTypeCount = ReaderContext.TablesStream.GetTable(TableIndex.NestedClass).Count; var types = new OwnedCollection(this, typeDefTable.Count - nestedTypeCount); @@ -232,9 +202,7 @@ protected override IList GetTopLevelTypes() /// protected override IList GetAssemblyReferences() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.AssemblyRef); + var table = ReaderContext.TablesStream.GetTable(TableIndex.AssemblyRef); var result = new OwnedCollection(this, table.Count); @@ -251,9 +219,7 @@ protected override IList GetAssemblyReferences() /// protected override IList GetModuleReferences() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.ModuleRef); + var table = ReaderContext.TablesStream.GetTable(TableIndex.ModuleRef); var result = new OwnedCollection(this, table.Count); @@ -270,9 +236,7 @@ protected override IList GetModuleReferences() /// protected override IList GetFileReferences() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.File); + var table = ReaderContext.TablesStream.GetTable(TableIndex.File); var result = new OwnedCollection(this, table.Count); @@ -289,9 +253,7 @@ protected override IList GetFileReferences() /// protected override IList GetResources() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.ManifestResource); + var table = ReaderContext.TablesStream.GetTable(TableIndex.ManifestResource); var result = new OwnedCollection(this, table.Count); @@ -308,9 +270,7 @@ protected override IList GetResources() /// protected override IList GetExportedTypes() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.ExportedType); + var table = ReaderContext.TablesStream.GetTable(TableIndex.ExportedType); var result = new OwnedCollection(this, table.Count); @@ -350,9 +310,7 @@ protected override IList GetExportedTypes() private AssemblyDefinition? FindParentAssembly() { - var assemblyTable = ReaderContext.Metadata - .GetStream() - .GetTable(); + var assemblyTable = ReaderContext.TablesStream.GetTable(); if (assemblyTable.Count > 0) { diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleReference.cs index 99f824eb8..64cf93396 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleReference.cs @@ -33,12 +33,7 @@ public SerializedModuleReference(ModuleReaderContext context, MetadataToken toke } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override IList GetCustomAttributes() => diff --git a/src/AsmResolver.DotNet/Serialized/SerializedParameterDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedParameterDefinition.cs index f87ad733c..f20a2dff3 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedParameterDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedParameterDefinition.cs @@ -36,12 +36,7 @@ public SerializedParameterDefinition(ModuleReaderContext context, MetadataToken } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override MethodDefinition? GetMethod() diff --git a/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs index b1f1fa227..d2dd3ae74 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs @@ -36,17 +36,12 @@ public SerializedPropertyDefinition(ModuleReaderContext context, MetadataToken t } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override PropertySignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Type, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedSecurityDeclaration.cs b/src/AsmResolver.DotNet/Serialized/SerializedSecurityDeclaration.cs index b6ced6c94..c39273a75 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedSecurityDeclaration.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedSecurityDeclaration.cs @@ -49,7 +49,7 @@ public class SerializedSecurityDeclaration : SecurityDeclaration /// protected override PermissionSetSignature? GetPermissionSet() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.PermissionSet, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedStandAloneSignature.cs b/src/AsmResolver.DotNet/Serialized/SerializedStandAloneSignature.cs index 981a8d6d0..49ab50915 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedStandAloneSignature.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedStandAloneSignature.cs @@ -36,7 +36,7 @@ public class SerializedStandAloneSignature : StandAloneSignature /// protected override CallingConventionSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs index 2ee5afe6b..ba495ba5c 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs @@ -36,20 +36,10 @@ public SerializedTypeDefinition(ModuleReaderContext context, MetadataToken token } /// - protected override Utf8String? GetNamespace() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Namespace) - : null; - } + protected override Utf8String? GetNamespace() => _context.StringsStream?.GetStringByIndex(_row.Namespace); /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override ITypeDefOrRef? GetBaseType() @@ -57,8 +47,7 @@ public SerializedTypeDefinition(ModuleReaderContext context, MetadataToken token if (_row.Extends == 0) return null; - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeDefOrRef) .DecodeIndex(_row.Extends); @@ -163,7 +152,7 @@ protected override IList GetInterfaces() /// protected override IList GetMethodImplementations() { - var tablesStream = _context.Metadata.GetStream(); + var tablesStream = _context.TablesStream; var table = tablesStream.GetTable(TableIndex.MethodImpl); var encoder = tablesStream.GetIndexEncoder(CodedIndex.MethodDefOrRef); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs index 02ca6986e..4e0cb839b 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs @@ -32,20 +32,10 @@ public SerializedTypeReference(ModuleReaderContext context, MetadataToken token, } /// - protected override Utf8String? GetNamespace() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Namespace) - : null; - } + protected override Utf8String? GetNamespace() => _context.StringsStream?.GetStringByIndex(_row.Namespace); /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override IResolutionScope? GetScope() @@ -53,7 +43,7 @@ public SerializedTypeReference(ModuleReaderContext context, MetadataToken token, if (_row.ResolutionScope == 0) return _context.ParentModule; - var tablesStream = _context.Metadata.GetStream(); + var tablesStream = _context.TablesStream; var decoder = tablesStream.GetIndexEncoder(CodedIndex.ResolutionScope); var token = decoder.DecodeIndex(_row.ResolutionScope); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeSpecification.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeSpecification.cs index f8f8c7aad..47f29d5ac 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeSpecification.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeSpecification.cs @@ -34,7 +34,7 @@ public SerializedTypeSpecification(ModuleReaderContext context, MetadataToken to /// protected override TypeSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( From 514f31de52ff00cf7c1061209e3966d5468dbb68 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 4 Sep 2022 19:59:08 +0200 Subject: [PATCH 144/182] Only compute value of TablesStream.IsEnCMetadata once. --- .../Builder/Metadata/MetadataBuffer.cs | 1 + .../Metadata/Tables/TablesStreamBuffer.cs | 6 ++-- .../DotNet/Metadata/Metadata.cs | 13 ++------ .../DotNet/Metadata/MetadataSignature.cs | 8 ++--- .../DotNet/Metadata/MetadataStreamList.cs | 29 ++++++----------- .../DotNet/Metadata/SerializedMetadata.cs | 32 +++++++++++++------ 6 files changed, 41 insertions(+), 48 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs index 05b7fbe95..ee5b91909 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs @@ -88,6 +88,7 @@ public IMetadata CreateMetadata() var result = new PE.DotNet.Metadata.Metadata { VersionString = _versionString, + IsEnCMetadata = TablesStream.IsEnCMetadata }; // Create and add streams. diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs index 49651b052..5ef435dbe 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs @@ -74,7 +74,7 @@ public TablesStreamBuffer() } /// - public string Name => HasEnCData ? TablesStream.EncStreamName : TablesStream.CompressedStreamName; + public string Name => IsEnCMetadata ? TablesStream.EncStreamName : TablesStream.CompressedStreamName; /// public bool IsEmpty @@ -92,9 +92,9 @@ public bool IsEmpty } /// - /// Gets a value indicating whether the buffer contains edit-and-continue data. + /// Gets a value indicating whether the buffer contains edit-and-continue metadata tables. /// - public bool HasEnCData + public bool IsEnCMetadata { get { diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs index e89caebd0..446d9f602 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs @@ -54,17 +54,8 @@ public ushort Flags /// public bool IsEnCMetadata { - get - { - var streams = Streams; - for (int i = 0; i < streams.Count; i++) - { - if (streams[i].Name == TablesStream.EncStreamName) - return true; - } - - return false; - } + get; + set; } /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataSignature.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataSignature.cs index c94db9875..1966758df 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/MetadataSignature.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataSignature.cs @@ -9,10 +9,10 @@ public enum MetadataSignature /// Indicates the BSJB metadata directory format is used. /// Bsjb = 0x424A5342, - + /// - /// Indicates the legacy +MOC metadata directory format is used. + /// Indicates the legacy COM+ metadata directory format is used. /// - Moc = 0x2B4D4F43 + ComPlus = 0x2B4D4F43 } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs index 0fe56918b..e477f8223 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs @@ -10,50 +10,39 @@ namespace AsmResolver.PE.DotNet.Metadata public class MetadataStreamList : LazyList { private readonly MetadataReaderContext _context; + private readonly MetadataStreamHeader[] _streamHeaders; private readonly IMetadata _owner; - private readonly int _numberOfStreams; private readonly BinaryStreamReader _directoryReader; - private BinaryStreamReader _entriesReader; /// /// Prepares a new lazy-initialized metadata stream list. /// /// The owner of the metadata stream list. /// The reader context. + /// The stream headers. /// The input stream containing the metadata directory. - /// The input stream containing the metadata stream entries. - /// The number of streams. public MetadataStreamList( IMetadata owner, MetadataReaderContext context, - in BinaryStreamReader directoryReader, - in BinaryStreamReader entriesReader, - int numberOfStreams) + MetadataStreamHeader[] streamHeaders, + in BinaryStreamReader directoryReader) { _context = context ?? throw new ArgumentNullException(nameof(context)); + _streamHeaders = streamHeaders; _owner = owner; _directoryReader = directoryReader; - _entriesReader = entriesReader; - _numberOfStreams = numberOfStreams; } /// - public override int Count => IsInitialized ? Items.Count : _numberOfStreams; + public override int Count => IsInitialized ? Items.Count : _streamHeaders.Length; /// protected override void Initialize() { - var headers = new MetadataStreamHeader[_numberOfStreams]; - for (int i = 0; i < _numberOfStreams; i++) - headers[i] = MetadataStreamHeader.FromReader(ref _entriesReader); - - for (int i = 0; i < _numberOfStreams; i++) + foreach (var header in _streamHeaders) { - var header = headers[i]; - - var streamReader = _directoryReader.ForkAbsolute(_directoryReader.Offset + header.Offset, headers[i].Size); + var streamReader = _directoryReader.ForkAbsolute(_directoryReader.Offset + header.Offset, header.Size); var stream = _context.MetadataStreamReader.ReadStream(_context, header, ref streamReader); - Items.Add(stream); } } @@ -61,7 +50,7 @@ protected override void Initialize() /// protected override void PostInitialize() { - for (int i = 0; i < _numberOfStreams; i++) + for (int i = 0; i < Items.Count; i++) { if (Items[i] is ILazyMetadataStream lazyStream) lazyStream.Initialize(_owner); diff --git a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs index 74cecd3da..629287c2b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Tables; namespace AsmResolver.PE.DotNet.Metadata { @@ -11,9 +12,8 @@ namespace AsmResolver.PE.DotNet.Metadata public class SerializedMetadata : Metadata { private readonly MetadataReaderContext _context; - private readonly BinaryStreamReader _streamEntriesReader; private readonly BinaryStreamReader _streamContentsReader; - private readonly int _numberOfStreams; + private readonly MetadataStreamHeader[] _streamHeaders; /// /// Reads a metadata directory from an input stream. @@ -34,14 +34,15 @@ public SerializedMetadata(MetadataReaderContext context, ref BinaryStreamReader _streamContentsReader = directoryReader.Fork(); + // Verify signature. var signature = (MetadataSignature) directoryReader.ReadUInt32(); switch (signature) { case MetadataSignature.Bsjb: // BSJB header is the default header. break; - case MetadataSignature.Moc: - _context.NotSupported("Old +MOC metadata header format is not supported."); + case MetadataSignature.ComPlus: + _context.NotSupported("Old COM+ metadata header format is not supported."); return; default: @@ -49,10 +50,12 @@ public SerializedMetadata(MetadataReaderContext context, ref BinaryStreamReader return; } + // Header fields. MajorVersion = directoryReader.ReadUInt16(); MinorVersion = directoryReader.ReadUInt16(); Reserved = directoryReader.ReadUInt32(); + // Version string. uint versionLength = directoryReader.ReadUInt32(); if (!directoryReader.CanRead(versionLength)) { @@ -60,25 +63,34 @@ public SerializedMetadata(MetadataReaderContext context, ref BinaryStreamReader return; } - var versionBytes = new byte[versionLength]; + byte[] versionBytes = new byte[versionLength]; directoryReader.ReadBytes(versionBytes, 0, versionBytes.Length); VersionString = Encoding.ASCII.GetString(versionBytes); + // Remainder of all header fields. Flags = directoryReader.ReadUInt16(); - _numberOfStreams = directoryReader.ReadInt16(); - _streamEntriesReader = directoryReader.Fork(); + int numberOfStreams = directoryReader.ReadInt16(); + + // Eagerly read stream headers to determine if we are EnC metadata. + _streamHeaders = new MetadataStreamHeader[numberOfStreams]; + for (int i = 0; i < numberOfStreams; i++) + { + _streamHeaders[i] = MetadataStreamHeader.FromReader(ref directoryReader); + if (_streamHeaders[i].Name == TablesStream.EncStreamName) + IsEnCMetadata = true; + } } /// protected override IList GetStreams() { - if (_numberOfStreams == 0) + if (_streamHeaders.Length == 0) return base.GetStreams(); return new MetadataStreamList(this, _context, - _streamContentsReader, - _streamEntriesReader, _numberOfStreams); + _streamHeaders, + _streamContentsReader); } } From 539d4a2b8f4f9d8fe81ebf60e47f9739fdf6b254 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 8 Sep 2022 20:34:45 +0200 Subject: [PATCH 145/182] Rename to IsEncMetadata for consistency. --- .../Builder/Metadata/MetadataBuffer.cs | 2 +- .../Builder/Metadata/Tables/TablesStreamBuffer.cs | 4 ++-- .../Serialized/ModuleReaderContext.cs | 10 +++++----- src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs | 2 +- src/AsmResolver.PE/DotNet/Metadata/Metadata.cs | 4 ++-- .../DotNet/Metadata/SerializedMetadata.cs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs index ee5b91909..4085377ee 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs @@ -88,7 +88,7 @@ public IMetadata CreateMetadata() var result = new PE.DotNet.Metadata.Metadata { VersionString = _versionString, - IsEnCMetadata = TablesStream.IsEnCMetadata + IsEncMetadata = TablesStream.IsEncMetadata }; // Create and add streams. diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs index 5ef435dbe..e9786577f 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs @@ -74,7 +74,7 @@ public TablesStreamBuffer() } /// - public string Name => IsEnCMetadata ? TablesStream.EncStreamName : TablesStream.CompressedStreamName; + public string Name => IsEncMetadata ? TablesStream.EncStreamName : TablesStream.CompressedStreamName; /// public bool IsEmpty @@ -94,7 +94,7 @@ public bool IsEmpty /// /// Gets a value indicating whether the buffer contains edit-and-continue metadata tables. /// - public bool IsEnCMetadata + public bool IsEncMetadata { get { diff --git a/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs b/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs index 7648fc42d..e5dafc8ff 100644 --- a/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs +++ b/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs @@ -38,7 +38,7 @@ public ModuleReaderContext(IPEImage image, SerializedModuleDefinition parentModu // On the other hand, EnC metadata (i.e., metadata with a #- stream) looks up the first occurrence // of the stream of the provided name. The exception for this is the tables stream itself, for which both // the CLR and CoreCLR seem to always result in a file corruption error when there are multiple table streams. - bool isEnCMetadata = Metadata.IsEnCMetadata; + bool isEncMetadata = Metadata.IsEncMetadata; for (int i = 0; i < Metadata.Streams.Count; i++) { @@ -47,16 +47,16 @@ public ModuleReaderContext(IPEImage image, SerializedModuleDefinition parentModu case TablesStream tablesStream when TablesStream is null: TablesStream = tablesStream; break; - case BlobStream blobStream when BlobStream is null || !isEnCMetadata: + case BlobStream blobStream when BlobStream is null || !isEncMetadata: BlobStream = blobStream; break; - case GuidStream guidStream when GuidStream is null || !isEnCMetadata: + case GuidStream guidStream when GuidStream is null || !isEncMetadata: GuidStream = guidStream; break; - case StringsStream stringsStream when StringsStream is null || !isEnCMetadata: + case StringsStream stringsStream when StringsStream is null || !isEncMetadata: StringsStream = stringsStream; break; - case UserStringsStream userStringsStream when UserStringsStream is null || !isEnCMetadata: + case UserStringsStream userStringsStream when UserStringsStream is null || !isEncMetadata: UserStringsStream = userStringsStream; break; } diff --git a/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs index 841ae7fc1..b49a1927c 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs @@ -63,7 +63,7 @@ ushort Flags /// /// Gets a value indicating whether the metadata directory is loaded as Edit-and-Continue metadata. /// - public bool IsEnCMetadata + public bool IsEncMetadata { get; } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs index 446d9f602..a7429495b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs @@ -52,7 +52,7 @@ public ushort Flags } /// - public bool IsEnCMetadata + public bool IsEncMetadata { get; set; @@ -255,7 +255,7 @@ public bool TryGetStream([NotNullWhen(true)] out TStream? stream) [NotNullWhen(true)] out IMetadataStream? stream) { var streams = Streams; - bool reverseOrder = heapRequested && !IsEnCMetadata; + bool reverseOrder = heapRequested && !IsEncMetadata; if (reverseOrder) { for (int i = streams.Count - 1; i >= 0; i--) diff --git a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs index 629287c2b..11ab40619 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs @@ -77,7 +77,7 @@ public SerializedMetadata(MetadataReaderContext context, ref BinaryStreamReader { _streamHeaders[i] = MetadataStreamHeader.FromReader(ref directoryReader); if (_streamHeaders[i].Name == TablesStream.EncStreamName) - IsEnCMetadata = true; + IsEncMetadata = true; } } From 034f85abdab648c06cc519441fb687986e19cf0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:06:27 +0000 Subject: [PATCH 146/182] Bump System.Text.Json from 6.0.5 to 6.0.6 Bumps [System.Text.Json](https://github.com/dotnet/runtime) from 6.0.5 to 6.0.6. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v6.0.5...v6.0.6) --- 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 8b9a38a50..edf05784b 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 - + From b6fa3edd613a0521c2fc9dbecb554605ac25706e Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 16 Sep 2022 15:12:52 +0200 Subject: [PATCH 147/182] Replace Entrypoint with EntryPoint for consistency with Reflection and other APIs. --- .../Builder/DotNetDirectoryBuffer.cs | 18 ++++----- src/AsmResolver.DotNet/FileReference.cs | 2 +- ...gedEntrypoint.cs => IManagedEntryPoint.cs} | 6 +-- src/AsmResolver.DotNet/MethodDefinition.cs | 2 +- src/AsmResolver.DotNet/ModuleDefinition.cs | 40 +++++++++---------- .../Serialized/SerializedModuleDefinition.cs | 10 ++--- .../Headers/OptionalHeader.cs | 8 ++-- .../Builder/PEFileBuilderBase.cs | 10 ++--- .../DotNet/Builder/ManagedPEFileBuilder.cs | 36 ++++++++--------- src/AsmResolver.PE/DotNet/DotNetDirectory.cs | 6 +-- .../DotNet/DotNetDirectoryFlags.cs | 18 ++++----- src/AsmResolver.PE/DotNet/IDotNetDirectory.cs | 6 +-- .../DotNet/SerializedDotNetDirectory.cs | 2 +- src/AsmResolver.PE/Platforms/Amd64Platform.cs | 4 +- src/AsmResolver.PE/Platforms/I386Platform.cs | 4 +- src/AsmResolver.PE/Platforms/Platform.cs | 4 +- .../Builder/CilMethodBodySerializerTest.cs | 4 +- .../Builder/TokenMappingTest.cs | 10 ++--- .../AssemblyRefTokenPreservationTest.cs | 4 +- .../MemberRefTokenPreservationTest.cs | 4 +- .../TypeRefTokenPreservationTest.cs | 4 +- .../Cloning/MetadataClonerTest.cs | 2 +- .../Code/Cil/CilMethodBodyTest.cs | 6 +-- .../Code/Native/NativeMethodBodyTest.cs | 2 +- .../ImplementationMapTest.cs | 22 +++++----- .../MetadataResolverTest.cs | 6 +-- .../Signatures/SignatureComparerTest.cs | 2 +- .../PlatformInvoke.cs | 14 +++---- 28 files changed, 128 insertions(+), 128 deletions(-) rename src/AsmResolver.DotNet/{IManagedEntrypoint.cs => IManagedEntryPoint.cs} (62%) diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs index 3b0e5ad60..60fe3f5af 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs @@ -143,7 +143,7 @@ public DotNetDirectoryBuildResult CreateDirectory() { Metadata = Metadata.CreateMetadata(), DotNetResources = Resources.Size > 0 ? Resources.CreateDirectory() : null, - Entrypoint = GetEntrypoint(), + EntryPoint = GetEntryPoint(), Flags = Module.Attributes, StrongName = StrongNameSize > 0 ? new DataSegment(new byte[StrongNameSize]) : null, VTableFixups = VTableFixups.Directory.Count > 0 ? VTableFixups.Directory : null @@ -152,29 +152,29 @@ public DotNetDirectoryBuildResult CreateDirectory() return new DotNetDirectoryBuildResult(directory, _tokenMapping); } - private uint GetEntrypoint() + private uint GetEntryPoint() { - if (Module.ManagedEntrypoint is null) + if (Module.ManagedEntryPoint is null) return 0; - var entrypointToken = MetadataToken.Zero; + var entryPointToken = MetadataToken.Zero; - switch (Module.ManagedEntrypoint.MetadataToken.Table) + switch (Module.ManagedEntryPoint.MetadataToken.Table) { case TableIndex.Method: - entrypointToken = GetMethodDefinitionToken(Module.ManagedEntrypointMethod!); + entryPointToken = GetMethodDefinitionToken(Module.ManagedEntryPointMethod!); break; case TableIndex.File: - entrypointToken = AddFileReference((FileReference) Module.ManagedEntrypoint); + entryPointToken = AddFileReference((FileReference) Module.ManagedEntryPoint); break; default: - ErrorListener.MetadataBuilder($"Invalid managed entrypoint {Module.ManagedEntrypoint.SafeToString()}."); + ErrorListener.MetadataBuilder($"Invalid managed entry point {Module.ManagedEntryPoint.SafeToString()}."); break; } - return entrypointToken.ToUInt32(); + return entryPointToken.ToUInt32(); } private void AddMethodSemantics(MetadataToken ownerToken, IHasSemantics provider) diff --git a/src/AsmResolver.DotNet/FileReference.cs b/src/AsmResolver.DotNet/FileReference.cs index 8de6ba74a..8f6078e0e 100644 --- a/src/AsmResolver.DotNet/FileReference.cs +++ b/src/AsmResolver.DotNet/FileReference.cs @@ -12,7 +12,7 @@ namespace AsmResolver.DotNet public class FileReference : MetadataMember, IImplementation, - IManagedEntrypoint, + IManagedEntryPoint, IOwnedCollectionElement { private readonly LazyVariable _name; diff --git a/src/AsmResolver.DotNet/IManagedEntrypoint.cs b/src/AsmResolver.DotNet/IManagedEntryPoint.cs similarity index 62% rename from src/AsmResolver.DotNet/IManagedEntrypoint.cs rename to src/AsmResolver.DotNet/IManagedEntryPoint.cs index 9b0c3fb27..3b23ab1bd 100644 --- a/src/AsmResolver.DotNet/IManagedEntrypoint.cs +++ b/src/AsmResolver.DotNet/IManagedEntryPoint.cs @@ -2,9 +2,9 @@ { /// /// Represents a member that is either a method definition or a reference to an external file, that can be used to - /// indicate the managed entrypoint of a .NET module. + /// indicate the managed entry point of a .NET module. /// - public interface IManagedEntrypoint : IMetadataMember + public interface IManagedEntryPoint : IMetadataMember { } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs index 4a32048be..a65658523 100644 --- a/src/AsmResolver.DotNet/MethodDefinition.cs +++ b/src/AsmResolver.DotNet/MethodDefinition.cs @@ -26,7 +26,7 @@ public class MethodDefinition : IHasGenericParameters, IMemberForwarded, IHasSecurityDeclaration, - IManagedEntrypoint + IManagedEntryPoint { private readonly LazyVariable _name; private readonly LazyVariable _declaringType; diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 3e693c258..1bab39caf 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -44,7 +44,7 @@ public class ModuleDefinition : private IList? _assemblyReferences; private IList? _customAttributes; - private readonly LazyVariable _managedEntrypoint; + private readonly LazyVariable _managedEntryPoint; private IList? _moduleReferences; private IList? _fileReferences; private IList? _resources; @@ -269,7 +269,7 @@ protected ModuleDefinition(MetadataToken token) _mvid = new LazyVariable(GetMvid); _encId = new LazyVariable(GetEncId); _encBaseId = new LazyVariable(GetEncBaseId); - _managedEntrypoint = new LazyVariable(GetManagedEntrypoint); + _managedEntryPoint = new LazyVariable(GetManagedEntryPoint); _runtimeVersion = new LazyVariable(GetRuntimeVersion); _nativeResources = new LazyVariable(GetNativeResources); Attributes = DotNetDirectoryFlags.ILOnly; @@ -494,13 +494,13 @@ public bool IsStrongNameSigned } /// - /// Gets or sets a value indicating whether the .NET module has a native entrypoint or not. + /// Gets or sets a value indicating whether the .NET module has a native entry point or not. /// - public bool HasNativeEntrypoint + public bool HasNativeEntryPoint { - get => (Attributes & DotNetDirectoryFlags.NativeEntrypoint) != 0; - set => Attributes = (Attributes & ~DotNetDirectoryFlags.NativeEntrypoint) - | (value ? DotNetDirectoryFlags.NativeEntrypoint : 0); + get => (Attributes & DotNetDirectoryFlags.NativeEntryPoint) != 0; + set => Attributes = (Attributes & ~DotNetDirectoryFlags.NativeEntryPoint) + | (value ? DotNetDirectoryFlags.NativeEntryPoint : 0); } /// @@ -744,22 +744,22 @@ public IMetadataResolver MetadataResolver } /// - /// Gets or sets the managed method that is invoked when the .NET module is initialized. + /// Gets or sets the managed method that is invoked after the .NET module is loaded and initialized. /// - public MethodDefinition? ManagedEntrypointMethod + public MethodDefinition? ManagedEntryPointMethod { - get => ManagedEntrypoint as MethodDefinition; - set => ManagedEntrypoint = value; + get => ManagedEntryPoint as MethodDefinition; + set => ManagedEntryPoint = value; } /// - /// Gets or sets the managed entrypoint that is invoked when the .NET module is initialized. This is either a - /// method, or a reference to a secondary module containing the entrypoint method. + /// Gets or sets the managed entry point that is invoked when the .NET module is initialized. This is either a + /// method, or a reference to a secondary module containing the entry point method. /// - public IManagedEntrypoint? ManagedEntrypoint + public IManagedEntryPoint? ManagedEntryPoint { - get => _managedEntrypoint.Value; - set => _managedEntrypoint.Value = value; + get => _managedEntryPoint.Value; + set => _managedEntryPoint.Value = value; } /// @@ -1036,13 +1036,13 @@ public TypeDefinition GetOrCreateModuleType() protected virtual string GetRuntimeVersion() => KnownRuntimeVersions.Clr40; /// - /// Obtains the managed entrypoint of this module. + /// Obtains the managed entry point of this module. /// - /// The entrypoint. + /// The entry point. /// - /// This method is called upon initialization of the property. + /// This method is called upon initialization of the property. /// - protected virtual IManagedEntrypoint? GetManagedEntrypoint() => null; + protected virtual IManagedEntryPoint? GetManagedEntryPoint() => null; /// /// Obtains the native win32 resources directory of the underlying PE image (if available). diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index bacbaffea..9b7866ab4 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -288,16 +288,16 @@ protected override IList GetExportedTypes() protected override string GetRuntimeVersion() => ReaderContext.Metadata!.VersionString; /// - protected override IManagedEntrypoint? GetManagedEntrypoint() + protected override IManagedEntryPoint? GetManagedEntryPoint() { - if ((DotNetDirectory.Flags & DotNetDirectoryFlags.NativeEntrypoint) != 0) + if ((DotNetDirectory.Flags & DotNetDirectoryFlags.NativeEntryPoint) != 0) { - // TODO: native entrypoints. + // TODO: native entry points. return null; } - if (DotNetDirectory.Entrypoint != 0) - return LookupMember(DotNetDirectory.Entrypoint) as IManagedEntrypoint; + if (DotNetDirectory.EntryPoint != 0) + return LookupMember(DotNetDirectory.EntryPoint) as IManagedEntryPoint; return null; } diff --git a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs index 917865b82..7da148153 100644 --- a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs +++ b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs @@ -52,7 +52,7 @@ public static OptionalHeader FromReader(ref BinaryStreamReader reader, bool igno SizeOfCode = reader.ReadUInt32(), SizeOfInitializedData = reader.ReadUInt32(), SizeOfUninitializedData = reader.ReadUInt32(), - AddressOfEntrypoint = reader.ReadUInt32(), + AddressOfEntryPoint = reader.ReadUInt32(), BaseOfCode = reader.ReadUInt32() }; @@ -168,9 +168,9 @@ public uint SizeOfUninitializedData } /// - /// Gets or sets the relative virtual address to the entrypoint of the portable executable (PE) file. + /// Gets or sets the relative virtual address to the entry point of the portable executable (PE) file. /// - public uint AddressOfEntrypoint + public uint AddressOfEntryPoint { get; set; @@ -417,7 +417,7 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteUInt32(SizeOfCode); writer.WriteUInt32(SizeOfInitializedData); writer.WriteUInt32(SizeOfUninitializedData); - writer.WriteUInt32(AddressOfEntrypoint); + writer.WriteUInt32(AddressOfEntryPoint); writer.WriteUInt32(BaseOfCode); switch (Magic) diff --git a/src/AsmResolver.PE/Builder/PEFileBuilderBase.cs b/src/AsmResolver.PE/Builder/PEFileBuilderBase.cs index 7ce12829a..4f6730ac2 100644 --- a/src/AsmResolver.PE/Builder/PEFileBuilderBase.cs +++ b/src/AsmResolver.PE/Builder/PEFileBuilderBase.cs @@ -51,13 +51,13 @@ public virtual PEFile CreateFile(IPEImage image) protected abstract IEnumerable CreateDataDirectories(PEFile peFile, IPEImage image, TContext context); /// - /// Gets the relative virtual address (RVA) to the entrypoint of the PE file. + /// Gets the relative virtual address (RVA) to the entry point of the PE file. /// - /// The (incomplete) PE file containing the entrypoint. + /// The (incomplete) PE file containing the entry point. /// The image that the PE file was based on. /// The object containing the intermediate values used during the PE file construction. - /// The relative virtual address to the entrypoin. - protected abstract uint GetEntrypointAddress(PEFile peFile, IPEImage image, TContext context); + /// The relative virtual address to the entry point. + protected abstract uint GetEntryPointAddress(PEFile peFile, IPEImage image, TContext context); /// /// Gets the file alignment for the new PE file. @@ -144,7 +144,7 @@ protected virtual void ComputeOptionalHeaderFields(PEFile peFile, IPEImage image header.SizeOfImage += section.GetVirtualSize(); } - header.AddressOfEntrypoint = GetEntrypointAddress(peFile, image, context); + header.AddressOfEntryPoint = GetEntryPointAddress(peFile, image, context); header.BaseOfCode = peFile.Sections.FirstOrDefault(s => s.IsContentCode)?.Rva ?? 0; header.BaseOfData = peFile.Sections.FirstOrDefault(s => s.IsContentInitializedData)?.Rva ?? 0; diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs index 56fb4fb7d..d4883b951 100644 --- a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs +++ b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs @@ -134,7 +134,7 @@ public Platform Platform } /// - /// Gets the code segment used as a native entrypoint of the resulting PE file. + /// Gets the code segment used as a native entry point of the resulting PE file. /// /// /// This property might be null if no bootstrapper is to be emitted. For example, since the @@ -246,23 +246,23 @@ protected virtual PESection CreateTextSection(IPEImage image, ManagedPEBuilderCo private static void CreateImportDirectory(IPEImage image, ManagedPEBuilderContext context) { - bool importEntrypointRequired = context.Platform.IsClrBootstrapperRequired + bool importEntryPointRequired = context.Platform.IsClrBootstrapperRequired || (image.DotNetDirectory!.Flags & DotNetDirectoryFlags.ILOnly) == 0; - string entrypointName = (image.Characteristics & Characteristics.Dll) != 0 + string entryPointName = (image.Characteristics & Characteristics.Dll) != 0 ? "_CorDllMain" : "_CorExeMain"; - var modules = CollectImportedModules(image, importEntrypointRequired, entrypointName, out var entrypointSymbol); + var modules = CollectImportedModules(image, importEntryPointRequired, entryPointName, out var entryPointSymbol); foreach (var module in modules) context.ImportDirectory.AddModule(module); - if (importEntrypointRequired) + if (importEntryPointRequired) { - if (entrypointSymbol is null) - throw new InvalidOperationException("Entrypoint symbol was required but not imported."); + if (entryPointSymbol is null) + throw new InvalidOperationException("Entry point symbol was required but not imported."); - context.Bootstrapper = context.Platform.CreateThunkStub(entrypointSymbol); + context.Bootstrapper = context.Platform.CreateThunkStub(entryPointSymbol); } } @@ -270,23 +270,23 @@ private static void CreateImportDirectory(IPEImage image, ManagedPEBuilderContex IPEImage image, bool entryRequired, string mscoreeEntryName, - out ImportedSymbol? entrypointSymbol) + out ImportedSymbol? entryPointSymbol) { var modules = new List(); IImportedModule? mscoreeModule = null; - entrypointSymbol = null; + entryPointSymbol = null; foreach (var module in image.Imports) { - // Check if the CLR entrypoint is already imported. + // Check if the CLR entry point is already imported. if (module.Name == "mscoree.dll") { mscoreeModule = module; - // Find entrypoint in this imported module. + // Find entry point in this imported module. if (entryRequired) - entrypointSymbol = mscoreeModule.Symbols.FirstOrDefault(s => s.Name == mscoreeEntryName); + entryPointSymbol = mscoreeModule.Symbols.FirstOrDefault(s => s.Name == mscoreeEntryName); // Only include mscoree.dll if necessary. if (entryRequired || module.Symbols.Count > 1) @@ -308,11 +308,11 @@ private static void CreateImportDirectory(IPEImage image, ManagedPEBuilderContex modules.Add(mscoreeModule); } - // Add entrypoint sumbol if it wasn't imported yet. - if (entrypointSymbol is null) + // Add entry point sumbol if it wasn't imported yet. + if (entryPointSymbol is null) { - entrypointSymbol = new ImportedSymbol(0, mscoreeEntryName); - mscoreeModule.Symbols.Add(entrypointSymbol); + entryPointSymbol = new ImportedSymbol(0, mscoreeEntryName); + mscoreeModule.Symbols.Add(entryPointSymbol); } } @@ -423,7 +423,7 @@ protected override IEnumerable CreateDataDirectories(PEFile peFil } /// - protected override uint GetEntrypointAddress(PEFile peFile, IPEImage image, ManagedPEBuilderContext context) + protected override uint GetEntryPointAddress(PEFile peFile, IPEImage image, ManagedPEBuilderContext context) => context.Bootstrapper?.Segment.Rva ?? 0; /// diff --git a/src/AsmResolver.PE/DotNet/DotNetDirectory.cs b/src/AsmResolver.PE/DotNet/DotNetDirectory.cs index 30e6b4c58..b6f7e21ca 100644 --- a/src/AsmResolver.PE/DotNet/DotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/DotNetDirectory.cs @@ -62,7 +62,7 @@ public DotNetDirectoryFlags Flags } /// - public uint Entrypoint + public uint EntryPoint { get; set; @@ -116,7 +116,7 @@ public uint Entrypoint + 2 * sizeof(ushort) // version + DataDirectory.DataDirectorySize // metadata + sizeof(uint) // flags - + sizeof(uint) // entrypoint + + sizeof(uint) // entry point + 6 * DataDirectory.DataDirectorySize; // data directories. /// @@ -127,7 +127,7 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteUInt16(MinorRuntimeVersion); CreateDataDirectoryHeader(Metadata).Write(writer); writer.WriteUInt32((uint) Flags); - writer.WriteUInt32(Entrypoint); + writer.WriteUInt32(EntryPoint); CreateDataDirectoryHeader(DotNetResources).Write(writer); CreateDataDirectoryHeader(StrongName).Write(writer); CreateDataDirectoryHeader(CodeManagerTable).Write(writer); diff --git a/src/AsmResolver.PE/DotNet/DotNetDirectoryFlags.cs b/src/AsmResolver.PE/DotNet/DotNetDirectoryFlags.cs index 9e5b3074b..9d3bbc045 100644 --- a/src/AsmResolver.PE/DotNet/DotNetDirectoryFlags.cs +++ b/src/AsmResolver.PE/DotNet/DotNetDirectoryFlags.cs @@ -15,36 +15,36 @@ public enum DotNetDirectoryFlags /// Mixed-mode applications should set this flag to zero. /// ILOnly = 0x00000001, - + /// /// Indicates the .NET image requires a 32-bit architecture to run on. /// Bit32Required = 0x00000002, - + /// /// Indicates the .NET image is a .NET library. /// ILLibrary = 0x00000004, - + /// /// Indicates the .NET image is signed with a strong name. /// StrongNameSigned = 0x00000008, - + /// - /// Indicates the entrypoint defined in is a relative virtual address + /// Indicates the entry point defined in is a relative virtual address /// to a native function. /// - NativeEntrypoint = 0x00000010, - + NativeEntryPoint = 0x00000010, + /// /// Indicates the debug data is tracked. /// TrackDebugData = 0x00010000, - + /// /// Indicates the application will run in an 32-bit environment if it is possible. /// Bit32Preferred = 0x00020000 } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs index b832628d1..0e808191e 100644 --- a/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs @@ -52,10 +52,10 @@ DotNetDirectoryFlags Flags } /// - /// Gets or sets the metadata token or entrypoint virtual address, depending on whether - /// is set in . + /// Gets or sets the metadata token or entry point virtual address, depending on whether + /// is set in . /// - uint Entrypoint + uint EntryPoint { get; set; diff --git a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs index 6ab56fa40..203c6e485 100644 --- a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs @@ -39,7 +39,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader MinorRuntimeVersion = reader.ReadUInt16(); _metadataDirectory = DataDirectory.FromReader(ref reader); Flags = (DotNetDirectoryFlags) reader.ReadUInt32(); - Entrypoint = reader.ReadUInt32(); + EntryPoint = reader.ReadUInt32(); _resourcesDirectory = DataDirectory.FromReader(ref reader); _strongNameDirectory = DataDirectory.FromReader(ref reader); _codeManagerDirectory = DataDirectory.FromReader(ref reader); diff --git a/src/AsmResolver.PE/Platforms/Amd64Platform.cs b/src/AsmResolver.PE/Platforms/Amd64Platform.cs index f0257da2f..895e39c9d 100644 --- a/src/AsmResolver.PE/Platforms/Amd64Platform.cs +++ b/src/AsmResolver.PE/Platforms/Amd64Platform.cs @@ -25,14 +25,14 @@ public static Amd64Platform Instance public override bool IsClrBootstrapperRequired => false; /// - public override RelocatableSegment CreateThunkStub(ISymbol entrypoint) + public override RelocatableSegment CreateThunkStub(ISymbol entryPoint) { var segment = new CodeSegment(new byte[] { 0x48, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rex.w rex.b mov rax, [&symbol] 0xFF, 0xE0 // jmp [rax] }); - segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute64BitAddress, entrypoint)); + segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute64BitAddress, entryPoint)); return new RelocatableSegment(segment, new[] { diff --git a/src/AsmResolver.PE/Platforms/I386Platform.cs b/src/AsmResolver.PE/Platforms/I386Platform.cs index e5b6b0c4b..87c431ae0 100644 --- a/src/AsmResolver.PE/Platforms/I386Platform.cs +++ b/src/AsmResolver.PE/Platforms/I386Platform.cs @@ -25,13 +25,13 @@ public static I386Platform Instance public override bool IsClrBootstrapperRequired => true; /// - public override RelocatableSegment CreateThunkStub(ISymbol entrypoint) + public override RelocatableSegment CreateThunkStub(ISymbol entryPoint) { var segment = new CodeSegment(new byte[] { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 // jmp [&symbol] }); - segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute32BitAddress, entrypoint)); + segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute32BitAddress, entryPoint)); return new RelocatableSegment(segment, new[] { diff --git a/src/AsmResolver.PE/Platforms/Platform.cs b/src/AsmResolver.PE/Platforms/Platform.cs index 554525644..151b4da02 100644 --- a/src/AsmResolver.PE/Platforms/Platform.cs +++ b/src/AsmResolver.PE/Platforms/Platform.cs @@ -69,9 +69,9 @@ or MachineType.Amd64DotNetFreeBsd /// /// Creates a new thunk stub that transfers control to the provided symbol. /// - /// The symbol to jump to. + /// The symbol to jump to. /// The created stub. - public abstract RelocatableSegment CreateThunkStub(ISymbol entrypoint); + public abstract RelocatableSegment CreateThunkStub(ISymbol entryPoint); /// /// Attempts to extract the original RVA from the code at the provided thunk address reader. diff --git a/test/AsmResolver.DotNet.Tests/Builder/CilMethodBodySerializerTest.cs b/test/AsmResolver.DotNet.Tests/Builder/CilMethodBodySerializerTest.cs index 3dbf54fcf..92ea0f77b 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/CilMethodBodySerializerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/CilMethodBodySerializerTest.cs @@ -35,7 +35,7 @@ public void ComputeMaxStackOnBuildOverride(bool computeMaxStack, bool? computeMa }; module.GetOrCreateModuleType().Methods.Add(main); - module.ManagedEntrypoint = main; + module.ManagedEntryPoint = main; var builder = new ManagedPEImageBuilder(new DotNetDirectoryFactory { @@ -48,7 +48,7 @@ public void ComputeMaxStackOnBuildOverride(bool computeMaxStack, bool? computeMa var newImage = builder.CreateImage(module).ConstructedImage; var newModule = ModuleDefinition.FromImage(newImage); - Assert.Equal(expectedMaxStack, newModule.ManagedEntrypointMethod.CilMethodBody.MaxStack); + Assert.Equal(expectedMaxStack, newModule.ManagedEntryPointMethod.CilMethodBody.MaxStack); } } } \ No newline at end of file diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs index af428c6a4..514950b96 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs @@ -74,7 +74,7 @@ public void NewMethodDefinition() module.GetOrCreateModuleType().Methods.Add(method); // Get existing main method. - var main = module.ManagedEntrypointMethod; + var main = module.ManagedEntryPointMethod; // Rebuild. var builder = new ManagedPEImageBuilder(); @@ -134,7 +134,7 @@ public void NewMemberReference() var reference = importer.ImportMethod(typeof(MemoryStream).GetConstructor(Type.EmptyTypes)); // Ensure method reference is added to the module by referencing it in main. - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; instructions.Insert(0, CilOpCodes.Newobj, reference); instructions.Insert(1, CilOpCodes.Pop); @@ -162,7 +162,7 @@ public void NewTypeSpecification() var specification = importer.ImportType(typeof(List)); // Ensure method reference is added to the module by referencing it in main. - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; instructions.Insert(0, CilOpCodes.Ldtoken, specification); instructions.Insert(1, CilOpCodes.Pop); @@ -190,7 +190,7 @@ public void NewMethodSpecification() var reference = importer.ImportMethod(typeof(Array).GetMethod("Empty").MakeGenericMethod(typeof(object))); // Ensure method reference is added to the module by referencing it in main. - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; instructions.Insert(0, CilOpCodes.Call, reference); instructions.Insert(1, CilOpCodes.Pop); @@ -219,7 +219,7 @@ public void NewStandaloneSignature() importer.ImportMethodSignature(MethodSignature.CreateStatic(module.CorLibTypeFactory.Void))); // Ensure reference is added to the module by referencing it in main. - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; instructions.Insert(0, CilOpCodes.Ldnull); instructions.Insert(0, CilOpCodes.Calli, signature); diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs index 5f9c79830..8c8d51208 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs @@ -30,7 +30,7 @@ public void PreserveAssemblyRefsWithTypeRefRemovedShouldAtLeastHaveOriginalAssem var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalAssemblyRefs = GetMembers(module, TableIndex.AssemblyRef); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Ret); @@ -49,7 +49,7 @@ public void PreserveAssemblyRefsWithExtraImportShouldAtLeastHaveOriginalAssembly var importer = new ReferenceImporter(module); var exists = importer.ImportMethod(typeof(File).GetMethod("Exists", new[] {typeof(string)})!); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.RemoveAt(instructions.Count - 1); instructions.Add(CilOpCodes.Ldstr, "file.txt"); instructions.Add(CilOpCodes.Call, exists); diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs index 7d7665510..7eff9300e 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs @@ -30,7 +30,7 @@ public void PreserveMemberRefsWithTypeRefRemovedShouldAtLeastHaveOriginalMemberR var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalMemberRefs = GetMembers(module, TableIndex.MemberRef); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Ret); @@ -49,7 +49,7 @@ public void PreserveMemberRefsWithExtraImportShouldAtLeastHaveOriginalMemberRefs var importer = new ReferenceImporter(module); var readKey = importer.ImportMethod(typeof(Console).GetMethod("ReadKey", Type.EmptyTypes)!); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.RemoveAt(instructions.Count - 1); instructions.Add(CilOpCodes.Call, readKey); instructions.Add(CilOpCodes.Pop); diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs index fa233b1e8..45eabbd0d 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs @@ -31,7 +31,7 @@ public void PreserveTypeRefsWithTypeRefRemovedShouldAtLeastHaveOriginalTypeRefs( var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalTypeRefs = GetMembers(module, TableIndex.TypeRef); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Ret); @@ -50,7 +50,7 @@ public void PreserveTypeRefsWithExtraImportShouldAtLeastHaveOriginalTypeRefs() var importer = new ReferenceImporter(module); var readKey = importer.ImportMethod(typeof(Console).GetMethod("ReadKey", Type.EmptyTypes)!); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.RemoveAt(instructions.Count - 1); instructions.Add(CilOpCodes.Call, readKey); instructions.Add(CilOpCodes.Pop); diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index 9a8e72d2e..c21dab1fc 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -123,7 +123,7 @@ public void CloneHelloWorldProgramType() foreach (var type in result.ClonedTopLevelTypes) targetModule.TopLevelTypes.Add(type); - targetModule.ManagedEntrypointMethod = (MethodDefinition) result.ClonedMembers.First(m => m.Name == "Main"); + targetModule.ManagedEntryPointMethod = (MethodDefinition) result.ClonedMembers.First(m => m.Name == "Main"); _fixture .GetRunner() .RebuildAndRun(targetModule, "HelloWorld.exe", "Hello World!" + Environment.NewLine); diff --git a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs index 717853367..8d4953085 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs @@ -401,7 +401,7 @@ public void ReadInvalidMethodBodyErrorShouldAppearInDiagnostics() public void ExceptionHandlerWithHandlerEndOutsideOfMethodShouldResultInEndLabel() { var module = ModuleDefinition.FromBytes(Properties.Resources.HandlerEndAtEndOfMethodBody); - var body = module.ManagedEntrypointMethod.CilMethodBody; + var body = module.ManagedEntryPointMethod.CilMethodBody; Assert.Same(body.Instructions.EndLabel, body.ExceptionHandlers[0].HandlerEnd); body.VerifyLabels(); } @@ -602,7 +602,7 @@ public void SmallTryBlockStartingOnLargeOffsetShouldResultInFatFormat() public void ReadUserStringFromNormalMetadata() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleUserStringsStream); - var instruction = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions + var instruction = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions .First(i => i.OpCode.Code == CilCode.Ldstr); Assert.Equal("Hello Mars!!", instruction.Operand); @@ -612,7 +612,7 @@ public void ReadUserStringFromNormalMetadata() public void ReadUserStringFromEnCMetadata() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleUserStringsStream_EnC); - var instruction = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions + var instruction = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions .First(i => i.OpCode.Code == CilCode.Ldstr); Assert.Equal("Hello World!", instruction.Operand); diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index a41eae6e8..73918cb62 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -310,7 +310,7 @@ public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint module.CorLibTypeFactory.String) ); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Call, body.Owner); instructions.Add(CilOpCodes.Newobj, stringConstructor); diff --git a/test/AsmResolver.DotNet.Tests/ImplementationMapTest.cs b/test/AsmResolver.DotNet.Tests/ImplementationMapTest.cs index 38308f37f..d51d3aee7 100644 --- a/test/AsmResolver.DotNet.Tests/ImplementationMapTest.cs +++ b/test/AsmResolver.DotNet.Tests/ImplementationMapTest.cs @@ -28,17 +28,17 @@ private ImplementationMap RebuildAndLookup(ImplementationMap implementationMap) { using var stream = new MemoryStream(); implementationMap.MemberForwarded.Module.Write(stream); - + var newModule = ModuleDefinition.FromBytes(stream.ToArray()); var t = newModule.TopLevelTypes.First(t => t.Name == nameof(PlatformInvoke)); return t.Methods.First(m => m.Name == implementationMap.MemberForwarded.Name).ImplementationMap; } - + [Fact] public void ReadName() { var map = Lookup(nameof(PlatformInvoke.ExternalMethod)); - Assert.Equal("SomeEntrypoint", map.Name); + Assert.Equal("SomeEntryPoint", map.Name); } [Fact] @@ -49,27 +49,27 @@ public void PersistentName() var newMap = RebuildAndLookup(map); Assert.Equal(map.Name, newMap.Name); } - + [Fact] public void ReadScope() { var map = Lookup(nameof(PlatformInvoke.ExternalMethod)); Assert.Equal("SomeDll.dll", map.Scope.Name); } - + [Fact] public void PersistentScope() { var map = Lookup(nameof(PlatformInvoke.ExternalMethod)); - + var newModule = new ModuleReference("SomeOtherDll.dll"); map.MemberForwarded.Module.ModuleReferences.Add(newModule); map.Scope = newModule; - + var newMap = RebuildAndLookup(map); Assert.Equal(newModule.Name, newMap.Scope.Name); } - + [Fact] public void ReadMemberForwarded() { @@ -100,11 +100,11 @@ public void AddingAlreadyAddedMapToAnotherMemberShouldThrow() public void PersistentMemberForwarded() { var map = Lookup(nameof(PlatformInvoke.ExternalMethod)); - + var declaringType = (TypeDefinition) map.MemberForwarded.DeclaringType; var otherMethod = declaringType.Methods.First(m => m.Name == nameof(PlatformInvoke.NonImplementationMapMethod)); - + map.MemberForwarded.ImplementationMap = null; otherMethod.ImplementationMap = map; @@ -113,4 +113,4 @@ public void PersistentMemberForwarded() } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs index 48de4c707..06d4d5ca7 100644 --- a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs @@ -192,7 +192,7 @@ public void ResolveExportedMemberReference() resolver.AddToCache(assembly2, assembly2); // Resolve - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; Assert.NotNull(((IMethodDescriptor) instructions[0].Operand).Resolve()); } @@ -238,7 +238,7 @@ public void ResolveToOlderNetVersion() .TopLevelTypes.First(t => t.Name == "MyClass") .Methods.First(m => m.Name == "ThrowMe"); - var reference = (IMethodDescriptor) mainApp.ManagedEntrypointMethod!.CilMethodBody!.Instructions.First( + var reference = (IMethodDescriptor) mainApp.ManagedEntryPointMethod!.CilMethodBody!.Instructions.First( i => i.OpCode == CilOpCodes.Callvirt && ((IMethodDescriptor) i.Operand)?.Name == "ThrowMe") .Operand!; @@ -260,7 +260,7 @@ public void ResolveMethodWithoutHideBySig() .ToArray(); var helloWorld = ModuleDefinition.FromFile(typeof(HelloWorldVB.Program).Assembly.Location); - var resolved = helloWorld.ManagedEntrypointMethod!.CilMethodBody!.Instructions + var resolved = helloWorld.ManagedEntryPointMethod!.CilMethodBody!.Instructions .Where(x => x.OpCode == CilOpCodes.Call) .Select(x => ((IMethodDescriptor) x.Operand!).Resolve()) .ToArray(); diff --git a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs index 5ae715869..bbe5cd367 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs @@ -145,7 +145,7 @@ public void MatchForwardedNestedTypes() module.MetadataResolver.AssemblyResolver.AddToCache(library, library); forwarder.ManifestModule!.MetadataResolver.AssemblyResolver.AddToCache(library, library); - var referencedTypes = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions + var referencedTypes = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions .Where(i => i.OpCode.Code == CilCode.Call) .Select(i => ((IMethodDefOrRef) i.Operand!).DeclaringType) .Where(t => t.Name == "MyNestedClass") diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/PlatformInvoke.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/PlatformInvoke.cs index 46641b2aa..755903da3 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/PlatformInvoke.cs +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/PlatformInvoke.cs @@ -5,21 +5,21 @@ namespace AsmResolver.DotNet.TestCases.Methods { public class PlatformInvoke { - [DllImport("SomeDll.dll", EntryPoint = "SomeEntrypoint")] + [DllImport("SomeDll.dll", EntryPoint = "SomeEntryPoint")] public static extern void ExternalMethod(); - + [DllImport("SomeDll.dll")] public static extern void SimpleMarshaller([MarshalAs(UnmanagedType.Bool)] bool b); - + [DllImport("SomeDll.dll")] public static extern void LPArrayFixedSizeMarshaller([MarshalAs(UnmanagedType.LPArray, SizeConst = 10)] byte[] array); - + [DllImport("SomeDll.dll")] public static extern void LPArrayVariableSizeMarshaller([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] array, int count); - + [DllImport("SomeDll.dll")] public static extern void SafeArrayMarshaller([MarshalAs(UnmanagedType.SafeArray)] byte[] array); - + [DllImport("SomeDll.dll")] public static extern void SafeArrayMarshallerWithSubType([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)] byte[] array); @@ -44,4 +44,4 @@ public static void NonImplementationMapMethod() { } } -} \ No newline at end of file +} From d954be07d8dabcbcd2e97e521342d9f6213dca2e Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 16 Sep 2022 15:17:31 +0200 Subject: [PATCH 148/182] :wqPe32 and Pe32Plus -> PE32 and PE32Plus. --- src/AsmResolver.DotNet/ModuleDefinition.cs | 2 +- .../Headers/OptionalHeader.cs | 16 ++++++++-------- .../Headers/OptionalHeaderMagic.cs | 13 +++++++++---- src/AsmResolver.PE.File/PEFile.cs | 4 ++-- src/AsmResolver.PE.File/SerializedPEFile.cs | 2 +- .../DotNet/Builder/ManagedPEFileBuilder.cs | 2 +- .../DotNet/StrongName/StrongNameSigner.cs | 2 +- .../Imports/SerializedImportedModule.cs | 2 +- src/AsmResolver.PE/PEImage.cs | 2 +- src/AsmResolver.PE/PEReaderContext.cs | 2 +- src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs | 2 +- .../AssemblyResolverTest.cs | 4 ++-- .../Code/Native/NativeMethodBodyTest.cs | 4 ++-- .../MethodDefinitionTest.cs | 4 ++-- .../DotNet/Builder/ManagedPEFileBuilderTest.cs | 4 ++-- .../DotNet/Builder/MixedModeAssemblyTest.cs | 4 ++-- .../AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs | 2 +- 17 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 1bab39caf..ad569218e 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -574,7 +574,7 @@ public OptionalHeaderMagic PEKind { get; set; - } = OptionalHeaderMagic.Pe32; + } = OptionalHeaderMagic.PE32; /// /// Gets or sets the subsystem to use when running the underlying portable executable (PE) file. diff --git a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs index 7da148153..a72535fa3 100644 --- a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs +++ b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs @@ -58,11 +58,11 @@ public static OptionalHeader FromReader(ref BinaryStreamReader reader, bool igno switch (header.Magic) { - case OptionalHeaderMagic.Pe32: + case OptionalHeaderMagic.PE32: header.BaseOfData = reader.ReadUInt32(); header.ImageBase = reader.ReadUInt32(); break; - case OptionalHeaderMagic.Pe32Plus: + case OptionalHeaderMagic.PE32Plus: header.ImageBase = reader.ReadUInt64(); break; default: @@ -84,7 +84,7 @@ public static OptionalHeader FromReader(ref BinaryStreamReader reader, bool igno header.SubSystem = (SubSystem)reader.ReadUInt16(); header.DllCharacteristics = (DllCharacteristics)reader.ReadUInt16(); - if (header.Magic == OptionalHeaderMagic.Pe32) + if (header.Magic == OptionalHeaderMagic.PE32) { header.SizeOfStackReserve = reader.ReadUInt32(); header.SizeOfStackCommit = reader.ReadUInt32(); @@ -120,7 +120,7 @@ public OptionalHeaderMagic Magic { get; set; - } = OptionalHeaderMagic.Pe32; + } = OptionalHeaderMagic.PE32; /// /// Gets or sets the major linker version used to link the portable executable (PE) file. @@ -403,7 +403,7 @@ public IList DataDirectories public override uint GetPhysicalSize() { // TODO: make configurable? - return Magic == OptionalHeaderMagic.Pe32 ? 0xE0u : 0xF0u; + return Magic == OptionalHeaderMagic.PE32 ? 0xE0u : 0xF0u; } /// @@ -422,11 +422,11 @@ public override void Write(IBinaryStreamWriter writer) switch (Magic) { - case OptionalHeaderMagic.Pe32: + case OptionalHeaderMagic.PE32: writer.WriteUInt32(BaseOfData); writer.WriteUInt32((uint)ImageBase); break; - case OptionalHeaderMagic.Pe32Plus: + case OptionalHeaderMagic.PE32Plus: writer.WriteUInt64(ImageBase); break; default: @@ -448,7 +448,7 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteUInt16((ushort)SubSystem); writer.WriteUInt16((ushort)DllCharacteristics); - if (Magic == OptionalHeaderMagic.Pe32) + if (Magic == OptionalHeaderMagic.PE32) { writer.WriteUInt32((uint)SizeOfStackReserve); writer.WriteUInt32((uint)SizeOfStackCommit); diff --git a/src/AsmResolver.PE.File/Headers/OptionalHeaderMagic.cs b/src/AsmResolver.PE.File/Headers/OptionalHeaderMagic.cs index abc64a88c..842656ff2 100644 --- a/src/AsmResolver.PE.File/Headers/OptionalHeaderMagic.cs +++ b/src/AsmResolver.PE.File/Headers/OptionalHeaderMagic.cs @@ -8,11 +8,16 @@ public enum OptionalHeaderMagic : ushort /// /// Indicates the binary contains a 32-bit portable executable file. /// - Pe32 = 0x010B, - + PE32 = 0x010B, + /// /// Indicates the binary contains a 64-bit portable executable file. /// - Pe32Plus = 0x020B, + PE32Plus = 0x020B, + + /// + /// Indicates the binary contains a 64-bit portable executable file. + /// + PE64 = PE32Plus } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index 6ce7616e2..56cf6999d 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -372,7 +372,7 @@ public void UpdateHeaders() FileHeader.NumberOfSections = (ushort) Sections.Count; var relocation = new RelocationParameters(OptionalHeader.ImageBase, 0, 0, - OptionalHeader.Magic == OptionalHeaderMagic.Pe32); + OptionalHeader.Magic == OptionalHeaderMagic.PE32); FileHeader.UpdateOffsets(relocation.WithOffsetRva( DosHeader.NextHeaderOffset + 4, @@ -408,7 +408,7 @@ public void AlignSections() OptionalHeader.ImageBase, OptionalHeader.SizeOfHeaders.Align(OptionalHeader.FileAlignment), OptionalHeader.SizeOfHeaders.Align(OptionalHeader.SectionAlignment), - OptionalHeader.Magic == OptionalHeaderMagic.Pe32); + OptionalHeader.Magic == OptionalHeaderMagic.PE32); for (int i = 0; i < Sections.Count; i++) { diff --git a/src/AsmResolver.PE.File/SerializedPEFile.cs b/src/AsmResolver.PE.File/SerializedPEFile.cs index a29c30d1b..52710fc18 100644 --- a/src/AsmResolver.PE.File/SerializedPEFile.cs +++ b/src/AsmResolver.PE.File/SerializedPEFile.cs @@ -39,7 +39,7 @@ public SerializedPEFile(in BinaryStreamReader reader, PEMappingMode mode) FileHeader = FileHeader.FromReader(ref _reader); OptionalHeader = OptionalHeader.FromReader(ref _reader); _originalImageBase = OptionalHeader.ImageBase; - _is32Bit = OptionalHeader.Magic == OptionalHeaderMagic.Pe32; + _is32Bit = OptionalHeader.Magic == OptionalHeaderMagic.PE32; // Read section headers. _reader.Offset = OptionalHeader.Offset + FileHeader.SizeOfOptionalHeader; diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs index d4883b951..458daad75 100644 --- a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs +++ b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs @@ -66,7 +66,7 @@ public ManagedPEBuilderContext(IPEImage image) if (image.DotNetDirectory is null) throw new ArgumentException("Image does not contain a .NET directory."); - ImportDirectory = new ImportDirectoryBuffer(image.PEKind == OptionalHeaderMagic.Pe32); + ImportDirectory = new ImportDirectoryBuffer(image.PEKind == OptionalHeaderMagic.PE32); ExportDirectory = new ExportDirectoryBuffer(); DotNetSegment = new DotNetSegmentBuffer(image.DotNetDirectory); ResourceDirectory = new ResourceDirectoryBuffer(); diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNameSigner.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNameSigner.cs index 7dc1e3c1a..c2679ced9 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNameSigner.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNameSigner.cs @@ -101,7 +101,7 @@ public void SignImage(Stream imageStream, AssemblyHashAlgorithm hashAlgorithm) hashBuilder.ZeroRange(new OffsetRange(peChecksumOffset, peChecksumOffset + sizeof(uint))); // Zero certificate directory entry. - uint optionalHeaderSize = file.OptionalHeader.Magic == OptionalHeaderMagic.Pe32 + uint optionalHeaderSize = file.OptionalHeader.Magic == OptionalHeaderMagic.PE32 ? OptionalHeader.OptionalHeader32SizeExcludingDataDirectories : OptionalHeader.OptionalHeader64SizeExcludingDataDirectories; ulong certificateEntryOffset = file.OptionalHeader.Offset diff --git a/src/AsmResolver.PE/Imports/SerializedImportedModule.cs b/src/AsmResolver.PE/Imports/SerializedImportedModule.cs index c2a4c5e9b..da74d77c8 100644 --- a/src/AsmResolver.PE/Imports/SerializedImportedModule.cs +++ b/src/AsmResolver.PE/Imports/SerializedImportedModule.cs @@ -68,7 +68,7 @@ protected override IList GetSymbols() if (IsEmpty) return result; - bool is32Bit = _context.File.OptionalHeader.Magic == OptionalHeaderMagic.Pe32; + bool is32Bit = _context.File.OptionalHeader.Magic == OptionalHeaderMagic.PE32; (ulong ordinalMask, int pointerSize) = is32Bit ? (0x8000_0000ul, sizeof(uint)) : (0x8000_0000_0000_0000ul, sizeof(ulong)); diff --git a/src/AsmResolver.PE/PEImage.cs b/src/AsmResolver.PE/PEImage.cs index 814ef7926..f1e61fbad 100644 --- a/src/AsmResolver.PE/PEImage.cs +++ b/src/AsmResolver.PE/PEImage.cs @@ -209,7 +209,7 @@ public OptionalHeaderMagic PEKind { get; set; - } = OptionalHeaderMagic.Pe32; + } = OptionalHeaderMagic.PE32; /// public SubSystem SubSystem diff --git a/src/AsmResolver.PE/PEReaderContext.cs b/src/AsmResolver.PE/PEReaderContext.cs index f5f696aaa..ec20bdc7f 100644 --- a/src/AsmResolver.PE/PEReaderContext.cs +++ b/src/AsmResolver.PE/PEReaderContext.cs @@ -56,7 +56,7 @@ public PEReaderParameters Parameters public RelocationParameters GetRelocation(ulong offset, uint rva) { return new RelocationParameters(File.OptionalHeader.ImageBase, offset, rva, - File.OptionalHeader.Magic == OptionalHeaderMagic.Pe32); + File.OptionalHeader.Magic == OptionalHeaderMagic.PE32); } /// diff --git a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs index afece2777..539c08cc4 100644 --- a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs @@ -68,7 +68,7 @@ protected override TlsCallbackCollection GetCallbackFunctions() var file = _context.File; var optionalHeader = file.OptionalHeader; ulong imageBase = optionalHeader.ImageBase; - bool is32Bit = optionalHeader.Magic == OptionalHeaderMagic.Pe32; + bool is32Bit = optionalHeader.Magic == OptionalHeaderMagic.PE32; if (!file.TryCreateReaderAtRva((uint) (_addressOfCallbacks - imageBase), out var reader)) { diff --git a/test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs b/test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs index 540ac2f8c..8673fb4df 100644 --- a/test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs @@ -175,7 +175,7 @@ public void PreferResolveFromGac32If32BitAssembly(bool legacy) module.IsBit32Preferred = true; module.IsBit32Required = true; module.MachineType = MachineType.I386; - module.PEKind = OptionalHeaderMagic.Pe32; + module.PEKind = OptionalHeaderMagic.PE32; var resolved = module.CorLibTypeFactory.CorLibScope.GetAssembly()!.Resolve(); Assert.NotNull(resolved); @@ -196,7 +196,7 @@ public void PreferResolveFromGac64If64BitAssembly(bool legacy) module.IsBit32Preferred = false; module.IsBit32Required = false; module.MachineType = MachineType.Amd64; - module.PEKind = OptionalHeaderMagic.Pe32Plus; + module.PEKind = OptionalHeaderMagic.PE32Plus; var resolved = module.CorLibTypeFactory.CorLibScope.GetAssembly()!.Resolve(); Assert.NotNull(resolved); diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index 73918cb62..356163a4d 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -36,13 +36,13 @@ private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) module.Attributes &= ~DotNetDirectoryFlags.ILOnly; if (is32Bit) { - module.PEKind = OptionalHeaderMagic.Pe32; + module.PEKind = OptionalHeaderMagic.PE32; module.MachineType = MachineType.I386; module.Attributes |= DotNetDirectoryFlags.Bit32Required; } else { - module.PEKind = OptionalHeaderMagic.Pe32Plus; + module.PEKind = OptionalHeaderMagic.PE32Plus; module.MachineType = MachineType.Amd64; } diff --git a/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs index cb3e996f2..1e2a6fc8f 100644 --- a/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs @@ -216,13 +216,13 @@ private static AssemblyDefinition CreateDummyLibraryWithExport(int methodCount, { module.Attributes |= DotNetDirectoryFlags.Bit32Required; module.FileCharacteristics |= Characteristics.Dll | Characteristics.Machine32Bit; - module.PEKind = OptionalHeaderMagic.Pe32; + module.PEKind = OptionalHeaderMagic.PE32; module.MachineType = MachineType.I386; } else { module.FileCharacteristics |= Characteristics.Dll; - module.PEKind = OptionalHeaderMagic.Pe32Plus; + module.PEKind = OptionalHeaderMagic.PE32Plus; module.MachineType = MachineType.Amd64; } diff --git a/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs b/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs index 18d2e859c..d9678ea24 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs @@ -62,7 +62,7 @@ public void HelloWorld32BitTo64Bit() // Change machine type and pe kind to 64-bit image.MachineType = MachineType.Amd64; - image.PEKind = OptionalHeaderMagic.Pe32Plus; + image.PEKind = OptionalHeaderMagic.PE32Plus; // Rebuild var builder = new ManagedPEFileBuilder(); @@ -82,7 +82,7 @@ public void HelloWorld64BitTo32Bit() // Change machine type and pe kind to 32-bit image.MachineType = MachineType.I386; - image.PEKind = OptionalHeaderMagic.Pe32; + image.PEKind = OptionalHeaderMagic.PE32; // Rebuild var builder = new ManagedPEFileBuilder(); diff --git a/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs b/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs index 0e957f84e..b5e6988d6 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs @@ -34,13 +34,13 @@ private static void ReplaceBodyWithNativeCode(IPEImage image, CodeSegment body, if (is32bit) { image.MachineType = MachineType.I386; - image.PEKind = OptionalHeaderMagic.Pe32; + image.PEKind = OptionalHeaderMagic.PE32; image.DotNetDirectory.Flags |= DotNetDirectoryFlags.Bit32Required; } else { image.MachineType = MachineType.Amd64; - image.PEKind = OptionalHeaderMagic.Pe32Plus; + image.PEKind = OptionalHeaderMagic.PE32Plus; } // Access metadata. diff --git a/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs b/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs index a0dd01775..be18565ff 100644 --- a/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs @@ -143,7 +143,7 @@ public void Persistent(bool is32Bit) }, OptionalHeader = { - Magic = is32Bit ? OptionalHeaderMagic.Pe32 : OptionalHeaderMagic.Pe32Plus + Magic = is32Bit ? OptionalHeaderMagic.PE32 : OptionalHeaderMagic.PE32Plus }, Sections = { From 2f8edb29242ac5390ffedb2e6ad9fdc0b0f91ec1 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 16 Sep 2022 15:18:27 +0200 Subject: [PATCH 149/182] Update docs on capitalization changes. --- docs/dotnet/unmanaged-method-bodies.rst | 10 +++++----- docs/pefile/headers.rst | 2 +- docs/peimage/dotnet.rst | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/dotnet/unmanaged-method-bodies.rst b/docs/dotnet/unmanaged-method-bodies.rst index c5edc20d3..2e733ac28 100644 --- a/docs/dotnet/unmanaged-method-bodies.rst +++ b/docs/dotnet/unmanaged-method-bodies.rst @@ -23,23 +23,23 @@ To make the CLR treat the output file as a mixed mode application, the ``ILOnly` .. code-block:: csharp ModuleDefinition module = ... - module.Attributes &= ~DotNetDirectoryFlags.ILOnly; + module.IsILOnly = false; Furthermore, make sure the right architecture is specified. For example, for an x86 64-bit binary, use the following: .. code-block:: csharp - module.PEKind = OptionalHeaderMagic.Pe32Plus; + module.PEKind = OptionalHeaderMagic.PE32Plus; module.MachineType = MachineType.Amd64; - module.Attributes &= ~DotNetDirectoryFlags.Bit32Required; + module.IsBit32Required = false; For 32-bit x86 binaries, use the following: .. code-block:: csharp - module.PEKind = OptionalHeaderMagic.Pe32; + module.PEKind = OptionalHeaderMagic.PE32; module.MachineType = MachineType.I386; - module.Attributes |= DotNetDirectoryFlags.Bit32Required; + module.IsBit32Required = true; Flags for native methods diff --git a/docs/pefile/headers.rst b/docs/pefile/headers.rst index 18468f5b6..fc011d58d 100644 --- a/docs/pefile/headers.rst +++ b/docs/pefile/headers.rst @@ -7,6 +7,6 @@ After you obtained an instance of the ``PEFile`` class, it is possible to read a Console.WriteLine("e_flanew: {0:X8}", peFile.DosHeader.NextHeaderOffset); Console.WriteLine("Machine: {0:X8}", peFile.FileHeader.Machine); - Console.WriteLine("Entrypoint: {0:X8}", peFile.OptionalHeader.AddressOfEntrypoint); + Console.WriteLine("EntryPoint: {0:X8}", peFile.OptionalHeader.AddressOfEntryPoint); Every change made to these headers will be reflected in the output executable, however very little verification on these values is done. diff --git a/docs/peimage/dotnet.rst b/docs/peimage/dotnet.rst index 73e9175ac..a4f41da3b 100644 --- a/docs/peimage/dotnet.rst +++ b/docs/peimage/dotnet.rst @@ -12,7 +12,7 @@ The .NET data directory can be accessed by the ``IPEImage.DotNetDirectory`` prop IPEImage peImage = ... - Console.WriteLine("Managed entrypoint: {0:X8}", peImage.DotNetDirectory.Entrypoint); + Console.WriteLine("Managed entry point: {0:X8}", peImage.DotNetDirectory.EntryPoint); Metadata directory From f98e5b67b607524cfd9aca8571b07d4f015c38f5 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 16 Sep 2022 16:32:27 +0200 Subject: [PATCH 150/182] Fix typo in CallbackClonerListener, add InjectTypeClonerListener for auto type injection during cloning. --- ...eListener.cs => CallbackClonerListener.cs} | 14 +++--- .../Cloning/IMemberClonerListener.cs | 2 +- .../Cloning/InjectTypeClonerListener.cs | 34 +++++++++++++ .../Cloning/MemberCloner.cs | 6 +-- .../Cloning/MemberClonerListener.cs | 2 +- .../Cloning/MetadataClonerTest.cs | 48 ++++++++++++------- 6 files changed, 76 insertions(+), 30 deletions(-) rename src/AsmResolver.DotNet/Cloning/{CallbackCloneListener.cs => CallbackClonerListener.cs} (60%) create mode 100644 src/AsmResolver.DotNet/Cloning/InjectTypeClonerListener.cs diff --git a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs b/src/AsmResolver.DotNet/Cloning/CallbackClonerListener.cs similarity index 60% rename from src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs rename to src/AsmResolver.DotNet/Cloning/CallbackClonerListener.cs index 91b195a41..e2a956603 100644 --- a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs +++ b/src/AsmResolver.DotNet/Cloning/CallbackClonerListener.cs @@ -5,28 +5,28 @@ namespace AsmResolver.DotNet.Cloning /// /// This implementation that calls the to a callback action. /// - public class CallbackCloneListener : MemberClonerListener + public class CallbackClonerListener : MemberClonerListener { - private readonly Action _callback; + private readonly Action _callback; /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// /// The Callback used. - public CallbackCloneListener(Action callback) => + public CallbackClonerListener(Action callback) => _callback = callback; /// - /// Gets a singleton instance of the class that performs no operation + /// Gets a singleton instance of the class that performs no operation /// on any of the cloning procedure notifications. /// - public static CallbackCloneListener EmptyInstance + public static CallbackClonerListener EmptyInstance { get; } = new((_, _) => { }); /// - public override void OnClonedMember(IMetadataMember original, IMetadataMember cloned) => + public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) => _callback(original, cloned); } diff --git a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs index 1a6ecaada..79bcbaae2 100644 --- a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs +++ b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs @@ -10,7 +10,7 @@ public interface IMemberClonerListener /// /// The original member. /// The cloned member. - public void OnClonedMember(IMetadataMember original, IMetadataMember cloned); + public void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned); /// /// This function is called for every type that got cloned. /// diff --git a/src/AsmResolver.DotNet/Cloning/InjectTypeClonerListener.cs b/src/AsmResolver.DotNet/Cloning/InjectTypeClonerListener.cs new file mode 100644 index 000000000..d69c9e1b9 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/InjectTypeClonerListener.cs @@ -0,0 +1,34 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Implements a that injects all non-nested types into the target module. + /// + public class InjectTypeClonerListener : MemberClonerListener + { + /// + /// Creates a new instance of the type. + /// + /// The target module to inject into. + public InjectTypeClonerListener(ModuleDefinition targetModule) + { + TargetModule = targetModule; + } + + /// + /// Gets the target module to inject the types in. + /// + public ModuleDefinition TargetModule + { + get; + } + + /// + public override void OnClonedType(TypeDefinition original, TypeDefinition cloned) + { + if (!original.IsNested) + TargetModule.TopLevelTypes.Add(cloned); + + base.OnClonedType(original, cloned); + } + } +} diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 31ffc6f11..64acfe3a5 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -52,8 +52,8 @@ public MemberCloner(ModuleDefinition targetModule) /// /// The target module to copy the members into. /// The callback used in the cloner listener. - public MemberCloner(ModuleDefinition targetModule, Action callback) - : this(targetModule, new CallbackCloneListener(callback)) + public MemberCloner(ModuleDefinition targetModule, Action callback) + : this(targetModule, new CallbackClonerListener(callback)) { } @@ -79,7 +79,7 @@ public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listene { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); _importerFactory = importerFactory; - _clonerListener = clonerListener ?? CallbackCloneListener.EmptyInstance; + _clonerListener = clonerListener ?? CallbackClonerListener.EmptyInstance; } /// diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs index 49bea854a..0c527ff40 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs @@ -4,7 +4,7 @@ namespace AsmResolver.DotNet.Cloning public abstract class MemberClonerListener : IMemberClonerListener { /// - public virtual void OnClonedMember(IMetadataMember original, IMetadataMember cloned) { } + public virtual void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) { } /// public virtual void OnClonedEvent(EventDefinition original, EventDefinition cloned) { } /// diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index c21dab1fc..050c8991a 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -33,7 +33,7 @@ private static ModuleDefinition PrepareTempModule() assembly.Modules.Add(module); return module; } - + private static TypeDefinition CloneType(Type type, out TypeDefinition originalTypeDef) { var sourceModule = ModuleDefinition.FromFile(type.Module.Assembly.Location); @@ -55,7 +55,7 @@ private static TypeDefinition CloneType(Type type, out TypeDefinition originalTy return clonedType; } - + private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDefinition originalMethodDef) { var sourceModule = ModuleDefinition.FromFile(methodBase.Module.Assembly.Location); @@ -75,7 +75,7 @@ private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDef return clonedMethod; } - + private static FieldDefinition CloneIntializerField(FieldInfo field, out FieldDefinition originalFieldDef) { var sourceModule = ModuleDefinition.FromFile(field.Module.Assembly.Location); @@ -139,7 +139,7 @@ public void CloneBranchInstructions() .Select(i => i.Operand) .OfType() .ToArray(); - + var newBranches = clonedMethod.CilMethodBody.Instructions .Where(i => i.IsBranch()) .Select(i => i.Operand) @@ -148,7 +148,7 @@ public void CloneBranchInstructions() // Assert offsets match. Assert.Equal( - originalBranches.Select(x => x.Offset), + originalBranches.Select(x => x.Offset), newBranches.Select(x => x.Offset)); // Assert all referenced instructions are instructions in the cloned method body. @@ -165,14 +165,14 @@ public void CloneSwitchInstruction() var originalBranches = (IEnumerable) method.CilMethodBody.Instructions .First(i => i.OpCode.Code == CilCode.Switch) .Operand; - + var newBranches = (IEnumerable) clonedMethod.CilMethodBody.Instructions .First(i => i.OpCode.Code == CilCode.Switch) .Operand; // Assert offsets match. Assert.Equal( - originalBranches.Select(x => x.Offset), + originalBranches.Select(x => x.Offset), newBranches.Select(x => x.Offset)); // Assert all referenced instructions are instructions in the cloned method body. @@ -186,7 +186,7 @@ public void CallToClonedMethods() { var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); - + var targetModule = PrepareTempModule(); var result = new MemberCloner(targetModule) @@ -212,7 +212,7 @@ public void ReferenceToNestedClass() { var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); - + var targetModule = PrepareTempModule(); var result = new MemberCloner(targetModule) @@ -222,7 +222,7 @@ public void ReferenceToNestedClass() var clonedMethod = (MethodDefinition) result.ClonedMembers .First(m => m.Name == nameof(Miscellaneous.NestedClassLocal)); - + var references = clonedMethod.CilMethodBody.Instructions .Where(i => i.OpCode.Code == CilCode.Callvirt || i.OpCode.Code == CilCode.Newobj) .Select(i => i.Operand) @@ -237,7 +237,7 @@ public void ReferenceToNestedClass() public void ReferencesToMethodSpecs() { // https://github.com/Washi1337/AsmResolver/issues/43 - + var clonedMethod = CloneMethod( typeof(GenericsTestClass).GetMethod(nameof(GenericsTestClass.MethodInstantiationFromExternalType)), out var method); @@ -253,7 +253,7 @@ public void ReferencesToMethodSpecs() Assert.Equal(originalSpec, newSpec, _signatureComparer); Assert.NotSame(originalSpec.Module, newSpec.Module); } - + [Fact] public void CloneImplMap() { @@ -268,7 +268,7 @@ public void CloneImplMap() public void CloneConstant() { var clonedMethod = CloneMethod(typeof(Miscellaneous).GetMethod(nameof(Miscellaneous.OptionalParameter)), out var method); - + Assert.NotEmpty(clonedMethod.ParameterDefinitions); Assert.NotNull(clonedMethod.ParameterDefinitions[0].Constant); Assert.Equal(clonedMethod.ParameterDefinitions[0].Constant.Type, method.ParameterDefinitions[0].Constant.Type); @@ -280,7 +280,7 @@ public void CloneFieldRva() { var clonedInitializerField = CloneIntializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field); - + var originalData = ((IReadableSegment) field.FieldRva).ToArray(); var newData = ((IReadableSegment) clonedInitializerField.FieldRva).ToArray(); @@ -339,11 +339,11 @@ public void CloneCallbackResult() var targetModule = PrepareTempModule(); - var reverseMethodsNames = (IMetadataMember original, IMetadataMember cloned) => { + var reverseMethodsNames = (IMemberDefinition original, IMemberDefinition cloned) => { if (cloned is MethodDefinition clonedDescriptor && original is MethodDefinition originalDescriptor) clonedDescriptor.Name = new string(originalDescriptor.Name.Reverse().ToArray()); - + }; var result = new MemberCloner(targetModule, reverseMethodsNames) @@ -355,13 +355,11 @@ public void CloneCallbackResult() Assert.Equal( type.Methods.Select(m => m.Name.Reverse().ToArray()), clonedType.Methods.Select(m => m.Name.ToArray())); - } [Fact] public void CloneCustomListenerResult() { - var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); @@ -376,7 +374,21 @@ public void CloneCustomListenerResult() Assert.Equal( type.Methods.Select(m => $"Method_{m.Name}"), clonedType.Methods.Select(m => m.Name.ToString())); + } + + [Fact] + public void CloneAndInject() + { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); + var targetModule = PrepareTempModule(); + + var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); + + var result = new MemberCloner(targetModule, new InjectTypeClonerListener(targetModule)) + .Include(type) + .Clone(); + Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes)); } } From c41d506927ad147fa8c9b43876ab0b7f8b9e5405 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 16 Sep 2022 16:32:35 +0200 Subject: [PATCH 151/182] Update docs on new API extensions for cloning. --- docs/dotnet/cloning.rst | 132 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 119 insertions(+), 13 deletions(-) diff --git a/docs/dotnet/cloning.rst b/docs/dotnet/cloning.rst index 540d448d9..8203fd86c 100644 --- a/docs/dotnet/cloning.rst +++ b/docs/dotnet/cloning.rst @@ -13,14 +13,14 @@ To help developers in injecting existing code into a module, ``AsmResolver.DotNe The MemberCloner class ---------------------- -The ``MemberCloner`` is the root object responsible for cloning members in a .NET module, and importing them into another. +The ``MemberCloner`` is the root object responsible for cloning members in a .NET module, and importing them into another. In the snippet below, we define a new ``MemberCloner`` that is able to clone and import members into the module ``destinationModule:``. .. code-block:: csharp ModuleDefinition destinationModule = ... - MemberCloner cloner = new MemberCloner(destinationModule); + var cloner = new MemberCloner(destinationModule); In the remaining sections of this article, we assume that the ``MemberCloner`` is initialized using the code above. @@ -36,7 +36,7 @@ For the sake of the example, we assume that the following two classes are to be public class Rectangle { - public Rectangle(Vector2 location, Vector2 size) + public Rectangle(Vector2 location, Vector2 size) { Location = location; Size = size; @@ -50,7 +50,7 @@ For the sake of the example, we assume that the following two classes are to be public class Vector2 { - public Vector2(int x, int y) + public Vector2(int x, int y) { X = x; Y = y; @@ -97,28 +97,122 @@ The ``recursive`` parameter indicates whether all members and nested types need Cloning individual methods, fields, properties and/or events is also supported. This can be done by including the corresponding ``MethodDefinition``, ``FieldDefinition``, ``PropertyDefinition`` and/or ``EventDefinition`` instead. -Cloning the included members +Cloning the included members ---------------------------- -When all members are included, it is possible to call ``MemberCloner.Clone`` to clone them all in one go. +When all members are included, it is possible to call ``MemberCloner.Clone`` to clone them all in one go. .. code-block:: csharp var result = cloner.Clone(); -The ``MemberCloner`` will automatically resolve any cross references between types, fields and methods that are included in the cloning process. +The ``MemberCloner`` will automatically resolve any cross references between types, fields and methods that are included in the cloning process. For instance, going with the example in the previous section, if both the ``Rectangle`` as well as the ``Vector2`` classes are included, any reference in ``Rectangle`` to ``Vector2`` will be replaced with a reference to the cloned ``Vector2``. If not all members are included, the ``MemberCloner`` will assume that these are references to external libraries, and will use the ``ReferenceImporter`` to construct references to these members instead. -.. warning:: - The ``MemberCloner`` heavily depends on the ``ReferenceImporter`` class for copying references into the destination module. This class has some limitations, in particular on importing / cloning from modules targeting different framework versions. See :ref:`dotnet-importer-common-caveats` for more information. +Custom reference importers +-------------------------- +The ``MemberCloner`` heavily depends on the ``CloneContextAwareReferenceImporter`` class for copying references into the destination module. This class is derived from ``ReferenceImporter``, which has some limitations. In particular, limitations arise when cloning from modules targeting different framework versions, or when trying to reference members that may already exist in the target module (e.g., when dealing with ``NullableAttribute`` annotated metadata). -Injecting the cloned members +To account for situations like these, the cloner allows for specifying custom reference importer instances. By deriving from the ``CloneContextAwareReferenceImporter`` class, and overriding methods such as ``ImportMethod``, we can reroute specific member references to the appropriate metadata if needed. Below is an example of a basic implementation of an importer that attempts to map method references from the ``System.Runtime.CompilerServices`` namespace to definitions that are already present in the target module. + +.. code-block:: csharp + + public class MyImporter : CloneContextAwareReferenceImporter + { + private static readonly SignatureComparer Comparer = new(); + + public MyImporter(MemberCloneContext context) + : base(context) + { + } + + public override IMethodDefOrRef ImportMethod(IMethodDefOrRef method) + { + // Check if the method is from a type defined in the System.Runtime.CompilerServices namespace. + if (method.DeclaringType is { Namespace.Value: "System.Runtime.CompilerServices" } type) + { + // We might already have a type and method defined in the target module (e.g., NullableAttribute::.ctor(int32)). + // Try find it in the target module. + + var existingMethod = this.Context.Module + .TopLevelTypes.FirstOrDefault(t => t.IsTypeOf(type.Namespace, type.Name))? + .Methods.FirstOrDefault(m => method.Name == m.Name && Comparer.Equals(m.Signature, method.Signature)); + + // If we found a matching definition, then return it instead of importing the reference. + if (existingMethod is not null) + return existingMethod; + } + + return base.ImportMethod(method); + } + } + + +We can then pass a custom importer factory to our member cloner constructor as follows: + +.. code-block:: csharp + + var cloner = new MemberCloner(destinationModule, context => new MyImporter(context)); + +All references to methods defined in the ``NSystem.Runtime.CompilerServices`` namespace will then be mapped to the appropriate method definitions if they exist in the target module. + +See :ref:`dotnet-importer-common-caveats` for more information on reference importing and its caveats. + + +Post processing of cloned members +--------------------------------- + +In some cases, cloned members may need to be post-processed before they are injected into the target module. The ``MemberCloner`` class can be initialized with an instance of a ``IMemberClonerListener``, that gets notified by the cloner object every time a definition was cloned. + +Below an example that appends the string ``_Cloned`` to the name for every cloned type. + +.. code-block:: csharp + + public class MyListener : MemberClonerListener + { + public override void OnClonedType(TypeDefinition original, TypeDefinition cloned) + { + cloned.Name = $"{original.Name}_Cloned"; + base.OnClonedType(original, cloned); + } + } + +We can then initialize our cloner with an instance of our listener class: + +.. code-block:: csharp + + var cloner = new MemberCloner(destinationModule, new MyListener()); + + +Alternatively, we can also override the more generic ``OnClonedMember`` instead, which gets fired for every member definition that was cloned. + +.. code-block:: csharp + + public class MyListener : MemberClonerListener + { + public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) + { + /* ... Do post processing here ... */ + base.OnClonedMember(original, cloned); + } + } + +As a shortcut, this can also be done by passing in a delegate or lambda instead to the ``MemberCloner`` constructor. + +.. code-block:: csharp + + var cloner = new MemberCloner(destinationModule, (original, cloned) => { + /* ... Do post processing here ... */ + }); + + +Injecting the cloned members ---------------------------- -After cloning, we obtain a ``MemberCloneResult``, which contains a register of all members cloned by the member cloner. +The ``Clone`` method returns a ``MemberCloneResult``, which contains a register of all members cloned by the member cloner. - ``OriginalMembers``: The collection containing all original members. - ``ClonedMembers``: The collection containing all cloned members. @@ -136,9 +230,21 @@ Alternatively, we can get all cloned top-level types. var clonedTypes = result.ClonedTopLevelTypes; -It is important to note that the ``MemberCloner`` class itself does not inject any of the cloned members. To inject the cloned types, we can for instance add them to the ``ModuleDefinition.TopLevelTypes`` collection: +It is important to note that the ``MemberCloner`` class itself does not inject any of the cloned members by itself. To inject the cloned types, we can for instance add them to the ``ModuleDefinition.TopLevelTypes`` collection: .. code-block:: csharp foreach (var clonedType in clonedTypes) - destinationModule.TopLevelTypes.Add(clonedType); \ No newline at end of file + destinationModule.TopLevelTypes.Add(clonedType); + + +However, since injecting the cloned top level types is a very common use-case for the cloner, AsmResolver defines the ``InjectTypeClonerListener`` class that implements a cloner listener that injects all top level types automatically into the destination module. In such a case, the code can be reduced to the following: + +.. code-block:: csharp + + new MemberCloner(destinationModule, new InjectTypeClonerListener(destinationModule)) + .Include(rectangleType) + .Include(vectorType) + .Clone(); + + // `destinationModule` now contains copies of `rectangleType` and `vectorType`. From 8fa4bfda63ee718b95c6cbb75cd48b997958f1b6 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 16 Sep 2022 17:20:01 +0200 Subject: [PATCH 152/182] Add check in ModuleDefinition::FromModule for invalid base addresses. --- src/AsmResolver.DotNet/ModuleDefinition.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index ad569218e..c463b7e2a 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -172,6 +172,8 @@ public static ModuleDefinition FromModule(Module module, ModuleReaderParameters var handle = (IntPtr) GetHINSTANCEMethod?.Invoke(null, new object[] { module })!; if (handle == IntPtr.Zero) throw new NotSupportedException("The current platform does not support getting the base address of an instance of System.Reflection.Module."); + if (handle == (IntPtr) (-1)) + throw new NotSupportedException("Provided module does not have a module base address."); // Dynamically loaded modules are in their unmapped form, as opposed to modules loaded normally by the // Windows PE loader. They also have a fully qualified name "" or similar. From ebbbbdfe93baeede5a7ad82954999107982a0025 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 16 Sep 2022 17:20:13 +0200 Subject: [PATCH 153/182] Add docs on reading and using dynamic methods. --- docs/dotnet/dynamic-methods.rst | 60 +++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 61 insertions(+) create mode 100644 docs/dotnet/dynamic-methods.rst diff --git a/docs/dotnet/dynamic-methods.rst b/docs/dotnet/dynamic-methods.rst new file mode 100644 index 000000000..398b77da2 --- /dev/null +++ b/docs/dotnet/dynamic-methods.rst @@ -0,0 +1,60 @@ +Dynamic Methods +=============== + +Dynamic methods are methods that are constructed and assembled at run time. They allow for dynamically generating managed code, without having to go through the process of compiling or generating new assemblies. This is used a lot in obfuscators that implement for example reference proxies or virtual machines. + +AsmResolver has support for reading dynamic methods, and transforming them into ``MethodDefinition`` objects that can be processed further later. All relevant classes are present in the following namespace: + +.. code-block:: csharp + + using AsmResolver.DotNet.Dynamic; + +.. note:: + + Since AsmResolver 5.0, this namespace exists in a separate ``AsmResolver.DotNet.Dynamic`` nuget package. + + +Reading dynamic methods +----------------------- + +The following example demonstrates how to transform an instance of ``DynamicMethod`` into a ``DynamicMethodDefinition``, to read all instructions present in + +.. code-block:: csharp + + DynamicMethod dynamicMethod = ... + + var contextModule = ModuleDefinition.FromFile(typeof(Program).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, dynamicMethod); + + +Note that the constructor requires a context module. This is the module that will be used to import or resolve any references within the method. + +.. warning:: + + Reading dynamic methods relies on dynamic analysis, and may therefore result in arbitrary code execution. Make sure to only use this in a safe environment if the input module is not trusted. + + +Using dynamic methods +--------------------- + +An instance of ``DynamicMethodDefinition`` is virtually the same as any other ``MethodDefinition``, and thus all its properties can be inspected and modified. Below an example that prints all the instructions that were present in the body of the dynamic method: + +.. code-block:: csharp + + DynamicMethodDefinition definition = ... + foreach (var instr in definition.CilMethodBody.Instructions) + Console.WriteLine(instr); + +``DynamicMethodDefinition`` are fully imported method definitions. This means we can safely add them to the context module: + +.. code-block:: csharp + + // Read dynamic method. + var contextModule = ModuleDefinition.FromFile(typeof(Program).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, dynamicMethod); + + // Add to type. + contextModule.GetOrCreateModuleType().Methods.Add(definition); + + // Save + contextModule.Write("Program.patched.dll"); diff --git a/docs/index.rst b/docs/index.rst index 89f3b0aeb..da9a805c7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -58,6 +58,7 @@ Table of Contents: dotnet/methods dotnet/managed-method-bodies dotnet/unmanaged-method-bodies + dotnet/dynamic-methods dotnet/managed-resources dotnet/cloning dotnet/token-allocation From 07e9ef4f5c3ed42155de15b2d45b801fa5463908 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 11:14:52 +0200 Subject: [PATCH 154/182] Add links to member tree and cil method bodies , fix some grammar and typos. --- docs/dotnet/dynamic-methods.rst | 7 +++++-- docs/dotnet/managed-method-bodies.rst | 1 + docs/dotnet/member-tree.rst | 13 ++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/dotnet/dynamic-methods.rst b/docs/dotnet/dynamic-methods.rst index 398b77da2..9b43317e7 100644 --- a/docs/dotnet/dynamic-methods.rst +++ b/docs/dotnet/dynamic-methods.rst @@ -3,7 +3,7 @@ Dynamic Methods Dynamic methods are methods that are constructed and assembled at run time. They allow for dynamically generating managed code, without having to go through the process of compiling or generating new assemblies. This is used a lot in obfuscators that implement for example reference proxies or virtual machines. -AsmResolver has support for reading dynamic methods, and transforming them into ``MethodDefinition`` objects that can be processed further later. All relevant classes are present in the following namespace: +AsmResolver has support for reading dynamic methods, and transforming them into ``MethodDefinition`` objects that can be processed further. All relevant classes are present in the following namespace: .. code-block:: csharp @@ -17,7 +17,7 @@ AsmResolver has support for reading dynamic methods, and transforming them into Reading dynamic methods ----------------------- -The following example demonstrates how to transform an instance of ``DynamicMethod`` into a ``DynamicMethodDefinition``, to read all instructions present in +The following example demonstrates how to transform an instance of ``DynamicMethod`` into a ``DynamicMethodDefinition``: .. code-block:: csharp @@ -58,3 +58,6 @@ An instance of ``DynamicMethodDefinition`` is virtually the same as any other `` // Save contextModule.Write("Program.patched.dll"); + + +See :ref:`dotnet-obtaining-methods-and-fields` and :ref:`dotnet-cil-method-bodies` for more information on how to use ``MethodDefinition`` objects. diff --git a/docs/dotnet/managed-method-bodies.rst b/docs/dotnet/managed-method-bodies.rst index 281be6f31..cc93dad27 100644 --- a/docs/dotnet/managed-method-bodies.rst +++ b/docs/dotnet/managed-method-bodies.rst @@ -1,3 +1,4 @@ +.. _dotnet-cil-method-bodies: CIL Method Bodies ================= diff --git a/docs/dotnet/member-tree.rst b/docs/dotnet/member-tree.rst index bffe00935..5e1c5df17 100644 --- a/docs/dotnet/member-tree.rst +++ b/docs/dotnet/member-tree.rst @@ -27,7 +27,7 @@ Below, an example program that iterates through all types recursively and prints .. code-block:: csharp public const int IndentationWidth = 3; - + private static void Main(string[] args) { var module = ModuleDefinition.FromFile(...); @@ -41,14 +41,14 @@ Below, an example program that iterates through all types recursively and prints { // Print the name of the current type. Console.WriteLine("{0}- {1} : {2:X8}", indentation, type.Name, type.MetadataToken.ToInt32()); - + // Dump any nested types. DumpTypes(type.NestedTypes, indentationLevel + 1); } } - -Obtaining methods and fields +.. _dotnet-obtaining-methods-and-fields: +Obtaining methods and fields ---------------------------- The ``TypeDefinition`` class exposes collections of methods and fields that the type defines: @@ -76,7 +76,7 @@ Methods and fields have a ``Signature`` property, that contain the return and pa .. code-block:: csharp FieldDefinition field = ... - Console.WriteLine("Return type: " + field.Signature.FieldType); + Console.WriteLine("Field type: " + field.Signature.FieldType); However, for reading parameters from a method definition, it is preferred to use the ``Parameters`` property instead of the ``ParameterTypes`` property stored in the signature. This is because the ``Parameters`` property automatically binds the types to the parameter definitions that are associated to these parameter types. This provides additional information, such as the name of the parameter: @@ -98,7 +98,7 @@ Obtaining properties and events is similar to obtaining methods and fields; ``Ty Console.WriteLine("{0} : {1:X8}", @event.Name, @event.MetadataToken.ToInt32()); .. code-block:: csharp - + foreach (var property in type.Properties) Console.WriteLine("{0} : {1:X8}", property.Name, property.MetadataToken.ToInt32()); @@ -112,4 +112,3 @@ Properties and events have methods associated to them. These are accessible thro Console.WriteLine("{0} {1} : {2:X8}", semantic.Attributes, semantic.Method.Name, semantic.MetadataToken.ToInt32()); } - From 89f754a1f77766d906a8d89eaff8ff342738c7db Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 11:15:28 +0200 Subject: [PATCH 155/182] Fix doc xml output paths. --- .../AsmResolver.DotNet.Dynamic.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj index 1f4ff4172..5519f663e 100644 --- a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj +++ b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj @@ -11,11 +11,11 @@ - bin\Debug\AsmResolver.DotNet.xml + bin\Debug\AsmResolver.DotNet.Dynamic.xml - bin\Release\AsmResolver.DotNet.xml + bin\Release\AsmResolver.DotNet.Dynamic.xml From 77d19c22acef28b0f15b16c1f91eac764a5bf9a1 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 11:26:15 +0200 Subject: [PATCH 156/182] Quick pass on member cloning docs. --- docs/dotnet/cloning.rst | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/dotnet/cloning.rst b/docs/dotnet/cloning.rst index 8203fd86c..871f6b499 100644 --- a/docs/dotnet/cloning.rst +++ b/docs/dotnet/cloning.rst @@ -25,10 +25,10 @@ In the snippet below, we define a new ``MemberCloner`` that is able to clone and In the remaining sections of this article, we assume that the ``MemberCloner`` is initialized using the code above. -Include members to clone ------------------------- +Include members +--------------- -The general idea of the ``MemberCloner`` is to first provide all the members to be cloned, and then clone everything all in one go. The reason why it is done like this, is to allow the ``MemberCloner`` to fix up any cross references to members within the to-be-cloned metadata and CIL code. +The general idea of the ``MemberCloner`` is to first provide all the members to be cloned, and then clone everything all in one go. This is to allow the ``MemberCloner`` to fix up any cross references to members within the to-be-cloned metadata and CIL code. For the sake of the example, we assume that the following two classes are to be injected in ``destinationModule``: @@ -60,7 +60,7 @@ For the sake of the example, we assume that the following two classes are to be public int Y { get; set; } } -The first thing we then should do, is find the type definitions that correspond to these classes: +The first step in cloning involves loading the source module, and finding the type definitions that correspond to these classes: .. code-block:: csharp @@ -68,6 +68,7 @@ The first thing we then should do, is find the type definitions that correspond var rectangleType = sourceModule.TopLevelTypes.First(t => t.Name == "Rectangle"); var vectorType = sourceModule.TopLevelTypes.First(t => t.Name == "Vector2"); + Alternatively, if the source assembly is loaded by the CLR, we also can look up the members by metadata token. .. code-block:: csharp @@ -84,15 +85,23 @@ We can then use ``MemberCloner.Include`` to include the types in the cloning pro cloner.Include(rectangleType, recursive: true); cloner.Include(vectorType, recursive: true); -The ``recursive`` parameter indicates whether all members and nested types need to be included as well. + +The ``recursive`` parameter indicates whether all members and nested types need to be included as well. This value is ``true`` by default and can also be omitted. + +.. code-block:: csharp + + cloner.Include(rectangleType); + cloner.Include(vectorType); + ``Include`` returns the same ``MemberCloner`` instance. It is therefore also possible to create a long method chain of members to include in the cloning process. .. code-block:: csharp cloner - .Include(rectangleType, recursive: true) - .Include(vectorType, recursive: true); + .Include(rectangleType) + .Include(vectorType); + Cloning individual methods, fields, properties and/or events is also supported. This can be done by including the corresponding ``MethodDefinition``, ``FieldDefinition``, ``PropertyDefinition`` and/or ``EventDefinition`` instead. @@ -106,6 +115,7 @@ When all members are included, it is possible to call ``MemberCloner.Clone`` to var result = cloner.Clone(); + The ``MemberCloner`` will automatically resolve any cross references between types, fields and methods that are included in the cloning process. For instance, going with the example in the previous section, if both the ``Rectangle`` as well as the ``Vector2`` classes are included, any reference in ``Rectangle`` to ``Vector2`` will be replaced with a reference to the cloned ``Vector2``. If not all members are included, the ``MemberCloner`` will assume that these are references to external libraries, and will use the ``ReferenceImporter`` to construct references to these members instead. @@ -116,7 +126,7 @@ Custom reference importers The ``MemberCloner`` heavily depends on the ``CloneContextAwareReferenceImporter`` class for copying references into the destination module. This class is derived from ``ReferenceImporter``, which has some limitations. In particular, limitations arise when cloning from modules targeting different framework versions, or when trying to reference members that may already exist in the target module (e.g., when dealing with ``NullableAttribute`` annotated metadata). -To account for situations like these, the cloner allows for specifying custom reference importer instances. By deriving from the ``CloneContextAwareReferenceImporter`` class, and overriding methods such as ``ImportMethod``, we can reroute specific member references to the appropriate metadata if needed. Below is an example of a basic implementation of an importer that attempts to map method references from the ``System.Runtime.CompilerServices`` namespace to definitions that are already present in the target module. +To account for these situations, the cloner allows for specifying custom reference importer instances. By deriving from the ``CloneContextAwareReferenceImporter`` class and overriding methods such as ``ImportMethod``, we can reroute specific member references to the appropriate metadata if needed. Below is an example of a basic implementation of an importer that attempts to map method references from the ``System.Runtime.CompilerServices`` namespace to definitions that are already present in the target module: .. code-block:: csharp @@ -157,7 +167,8 @@ We can then pass a custom importer factory to our member cloner constructor as f var cloner = new MemberCloner(destinationModule, context => new MyImporter(context)); -All references to methods defined in the ``NSystem.Runtime.CompilerServices`` namespace will then be mapped to the appropriate method definitions if they exist in the target module. + +All references to methods defined in the ``System.Runtime.CompilerServices`` namespace will then be mapped to the appropriate method definitions if they exist in the target module. See :ref:`dotnet-importer-common-caveats` for more information on reference importing and its caveats. @@ -180,6 +191,7 @@ Below an example that appends the string ``_Cloned`` to the name for every clon } } + We can then initialize our cloner with an instance of our listener class: .. code-block:: csharp @@ -200,6 +212,7 @@ Alternatively, we can also override the more generic ``OnClonedMember`` instead, } } + As a shortcut, this can also be done by passing in a delegate or lambda instead to the ``MemberCloner`` constructor. .. code-block:: csharp From 67a659efd04bdbac603482ab39527a75edd8938e Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 11:37:16 +0200 Subject: [PATCH 157/182] Add newlines to fix references in docs. --- docs/dotnet/managed-method-bodies.rst | 1 + docs/dotnet/member-tree.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/dotnet/managed-method-bodies.rst b/docs/dotnet/managed-method-bodies.rst index cc93dad27..3d438d3de 100644 --- a/docs/dotnet/managed-method-bodies.rst +++ b/docs/dotnet/managed-method-bodies.rst @@ -1,4 +1,5 @@ .. _dotnet-cil-method-bodies: + CIL Method Bodies ================= diff --git a/docs/dotnet/member-tree.rst b/docs/dotnet/member-tree.rst index 5e1c5df17..633d16808 100644 --- a/docs/dotnet/member-tree.rst +++ b/docs/dotnet/member-tree.rst @@ -48,6 +48,7 @@ Below, an example program that iterates through all types recursively and prints } .. _dotnet-obtaining-methods-and-fields: + Obtaining methods and fields ---------------------------- From 4ab6165cd9ace41bca169e9bd613584fe2f31022 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 14:51:27 +0200 Subject: [PATCH 158/182] Change BlobSerializationContext to a readonly struct. --- .../Signatures/BlobSerializationContext.cs | 2 +- src/AsmResolver.DotNet/Signatures/BlobSignature.cs | 2 +- .../Signatures/CustomAttributeArgumentWriter.cs | 2 +- .../Signatures/CustomAttributeSignature.cs | 2 +- src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs | 2 +- src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs | 4 ++-- src/AsmResolver.DotNet/Signatures/FieldSignature.cs | 2 +- .../Signatures/GenericInstanceMethodSignature.cs | 2 +- src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs | 2 +- .../Signatures/Marshal/ComInterfaceMarshalDescriptor.cs | 2 +- .../Signatures/Marshal/CustomMarshalDescriptor.cs | 2 +- .../Signatures/Marshal/FixedArrayMarshalDescriptor.cs | 2 +- .../Signatures/Marshal/FixedSysStringMarshalDescriptor.cs | 2 +- .../Signatures/Marshal/LPArrayMarshalDescriptor.cs | 2 +- .../Signatures/Marshal/SafeArrayMarshalDescriptor.cs | 2 +- .../Signatures/Marshal/SimpleMarshalDescriptor.cs | 2 +- src/AsmResolver.DotNet/Signatures/MethodSignature.cs | 2 +- src/AsmResolver.DotNet/Signatures/PropertySignature.cs | 2 +- .../Signatures/Security/PermissionSetSignature.cs | 2 +- .../Signatures/SerializedCustomAttributeSignature.cs | 2 +- .../Signatures/Types/ArrayTypeSignature.cs | 2 +- .../Signatures/Types/CorLibTypeSignature.cs | 2 +- .../Signatures/Types/CustomModifierTypeSignature.cs | 2 +- .../Signatures/Types/FunctionPointerTypeSignature.cs | 2 +- .../Signatures/Types/GenericInstanceTypeSignature.cs | 2 +- .../Signatures/Types/GenericParameterSignature.cs | 2 +- .../Signatures/Types/SentinelTypeSignature.cs | 2 +- .../Signatures/Types/TypeDefOrRefSignature.cs | 2 +- .../Signatures/Types/TypeSpecificationSignature.cs | 2 +- test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs | 5 ----- 30 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/BlobSerializationContext.cs b/src/AsmResolver.DotNet/Signatures/BlobSerializationContext.cs index bbe68c8ee..d53819ff3 100644 --- a/src/AsmResolver.DotNet/Signatures/BlobSerializationContext.cs +++ b/src/AsmResolver.DotNet/Signatures/BlobSerializationContext.cs @@ -6,7 +6,7 @@ namespace AsmResolver.DotNet.Signatures /// /// Describes a context in which a blob signature is to be serialized in. /// - public class BlobSerializationContext + public readonly struct BlobSerializationContext { /// /// Creates a new instance of the class. diff --git a/src/AsmResolver.DotNet/Signatures/BlobSignature.cs b/src/AsmResolver.DotNet/Signatures/BlobSignature.cs index 17ac56302..d42c16bcd 100644 --- a/src/AsmResolver.DotNet/Signatures/BlobSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/BlobSignature.cs @@ -8,7 +8,7 @@ public abstract class BlobSignature /// /// Serializes the blob to an output stream. /// - public abstract void Write(BlobSerializationContext context); + public abstract void Write(in BlobSerializationContext context); /// /// Wraps the blob signature into a new stand-alone signature that can be referenced by a metadata token. diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs index 71049ab4b..631bca86c 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs @@ -13,7 +13,7 @@ internal sealed class CustomAttributeArgumentWriter public CustomAttributeArgumentWriter(BlobSerializationContext context) { - _context = context ?? throw new ArgumentNullException(nameof(context)); + _context = context; } public void WriteArgument(CustomAttributeArgument argument) diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs index 3cb564c0c..8e645704a 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs @@ -144,7 +144,7 @@ public override string ToString() } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteUInt16(CustomAttributeSignaturePrologue); diff --git a/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs b/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs index 062d07a0d..8dee782cd 100644 --- a/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs @@ -65,7 +65,7 @@ public object InterpretData(ElementType elementType) } /// - public override void Write(BlobSerializationContext context) => context.Writer.WriteBytes(Data); + public override void Write(in BlobSerializationContext context) => context.Writer.WriteBytes(Data); /// /// Create a from a value diff --git a/src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs b/src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs index d19e4f9ae..10256d7a2 100644 --- a/src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs @@ -17,7 +17,7 @@ public abstract class ExtendableBlobSignature : BlobSignature } /// - public sealed override void Write(BlobSerializationContext context) + public sealed override void Write(in BlobSerializationContext context) { WriteContents(context); if (ExtraData is not null) @@ -27,6 +27,6 @@ public sealed override void Write(BlobSerializationContext context) /// /// Serializes the blob (without extra data) to an output stream. /// - protected abstract void WriteContents(BlobSerializationContext context); + protected abstract void WriteContents(in BlobSerializationContext context); } } diff --git a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs index 02b5495f4..f1d69e005 100644 --- a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs @@ -89,7 +89,7 @@ public TypeSignature FieldType GenericTypeActivator.Instance.InstantiateFieldSignature(this, context); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) Attributes); FieldType.Write(context); diff --git a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs index 291a07c25..33848a34d 100644 --- a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs @@ -93,7 +93,7 @@ public IList TypeArguments } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; writer.WriteByte((byte) Attributes); diff --git a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs index 2376fd95c..165e68558 100644 --- a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs @@ -93,7 +93,7 @@ public override bool IsImportedInModule(ModuleDefinition module) ImportWith(importer); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/ComInterfaceMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/ComInterfaceMarshalDescriptor.cs index c053be78c..96e60cbc5 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/ComInterfaceMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/ComInterfaceMarshalDescriptor.cs @@ -58,7 +58,7 @@ public override NativeType NativeType } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/CustomMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/CustomMarshalDescriptor.cs index 4376c82de..cdea0bda6 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/CustomMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/CustomMarshalDescriptor.cs @@ -87,7 +87,7 @@ public CustomMarshalDescriptor(string? guid, Utf8String? nativeTypeName, TypeSig } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/FixedArrayMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/FixedArrayMarshalDescriptor.cs index 6f9401a32..0e0e8cd98 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/FixedArrayMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/FixedArrayMarshalDescriptor.cs @@ -76,7 +76,7 @@ public FixedArrayMarshalDescriptor(int size, NativeType arrayElementType) } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/FixedSysStringMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/FixedSysStringMarshalDescriptor.cs index b41cd42cf..cbdf77aec 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/FixedSysStringMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/FixedSysStringMarshalDescriptor.cs @@ -41,7 +41,7 @@ public int Size } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/LPArrayMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/LPArrayMarshalDescriptor.cs index 58f98f65a..7346b3616 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/LPArrayMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/LPArrayMarshalDescriptor.cs @@ -83,7 +83,7 @@ public NativeType ArrayElementType } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/SafeArrayMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/SafeArrayMarshalDescriptor.cs index 0b4323254..85e2e2a8f 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/SafeArrayMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/SafeArrayMarshalDescriptor.cs @@ -135,7 +135,7 @@ public bool IsByRef } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/SimpleMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/SimpleMarshalDescriptor.cs index 81b5a4d34..e4f282186 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/SimpleMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/SimpleMarshalDescriptor.cs @@ -21,7 +21,7 @@ public override NativeType NativeType } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) NativeType); } diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs index 818452aa6..3600c6e32 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs @@ -201,7 +201,7 @@ public MethodSignature InstantiateGenericTypes(GenericContext context) public FunctionPointerTypeSignature MakeFunctionPointerType() => new(this); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/PropertySignature.cs b/src/AsmResolver.DotNet/Signatures/PropertySignature.cs index 5883cad30..15b01e0d1 100644 --- a/src/AsmResolver.DotNet/Signatures/PropertySignature.cs +++ b/src/AsmResolver.DotNet/Signatures/PropertySignature.cs @@ -112,7 +112,7 @@ public PropertySignature InstantiateGenericTypes(GenericContext context) => GenericTypeActivator.Instance.InstantiatePropertySignature(this, context); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) Attributes); WriteParametersAndReturnType(context); diff --git a/src/AsmResolver.DotNet/Signatures/Security/PermissionSetSignature.cs b/src/AsmResolver.DotNet/Signatures/Security/PermissionSetSignature.cs index 8ecf7e4f2..347365357 100644 --- a/src/AsmResolver.DotNet/Signatures/Security/PermissionSetSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Security/PermissionSetSignature.cs @@ -38,7 +38,7 @@ public IList Attributes } = new List(); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs index e1a6bc9de..3a841170a 100644 --- a/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs @@ -53,7 +53,7 @@ public class SerializedCustomAttributeSignature : CustomAttributeSignature } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { // If the arguments are not initialized yet, it means nobody accessed the fixed or named arguments of the // signature. In such a case, we can safely assume nothing has changed to the signature. diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs index e81140d88..081ec88e2 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs @@ -201,7 +201,7 @@ public bool Validate() visitor.VisitArrayType(this, state); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { if (!Validate()) throw new InvalidOperationException(); diff --git a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs index ae2a6974c..9f77eaba4 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs @@ -103,7 +103,7 @@ public override ElementType ElementType public override ITypeDefOrRef ToTypeDefOrRef() => Type; /// - protected override void WriteContents(BlobSerializationContext context) => + protected override void WriteContents(in BlobSerializationContext context) => context.Writer.WriteByte((byte) ElementType); /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs index 64ec085e1..12b4554ad 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs @@ -80,7 +80,7 @@ public override string Name ModifierType.IsImportedInModule(module) && base.IsImportedInModule(module); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) ElementType); WriteTypeDefOrRef(context, ModifierType, "Modifier type"); diff --git a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs index 1fe273fa5..b5ebe0c15 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs @@ -53,7 +53,7 @@ public MethodSignature Signature public override bool IsImportedInModule(ModuleDefinition module) => Signature.IsImportedInModule(module); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) ElementType); Signature.Write(context); diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 53a414283..33d588a03 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -127,7 +127,7 @@ public override bool IsImportedInModule(ModuleDefinition module) } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs index 24f487016..f29083c29 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs @@ -90,7 +90,7 @@ public int Index public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => null; /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; writer.WriteByte((byte) ElementType); diff --git a/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs index 49e7faa18..cf0e8e976 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs @@ -37,7 +37,7 @@ public class SentinelTypeSignature : TypeSignature public override bool IsImportedInModule(ModuleDefinition module) => true; /// - protected override void WriteContents(BlobSerializationContext context) => + protected override void WriteContents(in BlobSerializationContext context) => context.Writer.WriteByte((byte) ElementType); /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index f617beb31..3fed04061 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -83,7 +83,7 @@ public ITypeDefOrRef Type visitor.VisitTypeDefOrRef(this, state); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; writer.WriteByte((byte) ElementType); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs index 412964357..81ced7a33 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs @@ -44,7 +44,7 @@ public TypeSignature BaseType public override bool IsImportedInModule(ModuleDefinition module) => BaseType.IsImportedInModule(module); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) ElementType); WriteBaseType(context); diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index cfae099e9..e6eed8351 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -4,14 +4,9 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using AsmResolver.DotNet.Builder; -using AsmResolver.DotNet.Cloning; -using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.IO; -using AsmResolver.PE.DotNet.Builder; -using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.Win32Resources; using Xunit; From 0f50f84451e6029c02380e25f3bc7bf5bea19c2c Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 14:52:52 +0200 Subject: [PATCH 159/182] Change CustomAttributeArgumentWriter to a readonly struct. --- .../Signatures/CustomAttributeArgumentWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs index 631bca86c..a0319b23d 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs @@ -7,7 +7,7 @@ namespace AsmResolver.DotNet.Signatures { - internal sealed class CustomAttributeArgumentWriter + internal readonly struct CustomAttributeArgumentWriter { private readonly BlobSerializationContext _context; From d10cf47c1eef0a7974f162285310b38df4621f3f Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 16:12:49 +0200 Subject: [PATCH 160/182] Add equality methods to MethodImplementation. --- .../MethodImplementation.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/MethodImplementation.cs b/src/AsmResolver.DotNet/MethodImplementation.cs index bd4a18fc2..13ed8b7ac 100644 --- a/src/AsmResolver.DotNet/MethodImplementation.cs +++ b/src/AsmResolver.DotNet/MethodImplementation.cs @@ -1,9 +1,11 @@ +using System; + namespace AsmResolver.DotNet { /// /// Defines an explicit implementation of a method defined by an interface. /// - public readonly struct MethodImplementation + public readonly struct MethodImplementation : IEquatable { /// /// Creates a new explicit implementation of a method. @@ -33,7 +35,31 @@ public MethodImplementation(IMethodDefOrRef? declaration, IMethodDefOrRef? body) } /// - public override string ToString() => - $".override {Declaration} with {Body}"; + public override string ToString() => $".override {Declaration} with {Body}"; + + /// + /// Determines whether two method implementations record are equal. + /// + /// The other implementation record. + /// true if they are considered equal, false otherwise. + public bool Equals(MethodImplementation other) + { + return Equals(Declaration, other.Declaration) && Equals(Body, other.Body); + } + + /// + public override bool Equals(object? obj) + { + return obj is MethodImplementation other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((Declaration != null ? Declaration.GetHashCode() : 0) * 397) ^ (Body != null ? Body.GetHashCode() : 0); + } + } } } From 16367c198a9a50470a9cb780e83038c102ebe0f7 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 16:32:11 +0200 Subject: [PATCH 161/182] Remove lock object of lazy variable, and use 'this' instead. --- src/AsmResolver/LazyVariable.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver/LazyVariable.cs b/src/AsmResolver/LazyVariable.cs index 32d0627c6..c0f4775d7 100644 --- a/src/AsmResolver/LazyVariable.cs +++ b/src/AsmResolver/LazyVariable.cs @@ -7,11 +7,14 @@ namespace AsmResolver /// Represents a variable that can be lazily initialized and/or assigned a new value. /// /// The type of the values that the variable stores. + /// + /// For performance reasons, this class locks on itself for thread synchronization. Therefore, consumers + /// should not lock instances of this class as a lock object to avoid dead-locks. + /// public class LazyVariable { private T? _value; private readonly Func? _getValue; - private readonly object _lockObject = new(); /// /// Creates a new lazy variable and initialize it with a constant. @@ -55,7 +58,7 @@ public T Value } set { - lock (_lockObject) + lock (this) { _value = value; IsInitialized = true; @@ -65,7 +68,7 @@ public T Value private void InitializeValue() { - lock (_lockObject) + lock (this) { if (!IsInitialized) { From 3bb6b6ad33cf41b86411142a50332c0423020126 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 17:18:52 +0200 Subject: [PATCH 162/182] Consolidate MetadataRange types into a single readonly struct. --- .../Tables/ContinuousMetadataRange.cs | 75 ---------- .../DotNet/Metadata/Tables/IMetadataTable.cs | 11 +- .../DotNet/Metadata/Tables/MetadataRange.cs | 129 ++++++++++++++++-- .../DotNet/Metadata/Tables/MetadataTable.cs | 13 ++ .../Tables/RedirectedMetadataRange.cs | 95 ------------- .../DotNet/Metadata/Tables/TablesStream.cs | 4 +- .../Metadata/Tables/MetadataRangeTest.cs | 24 ++-- 7 files changed, 154 insertions(+), 197 deletions(-) delete mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/ContinuousMetadataRange.cs delete mode 100644 src/AsmResolver.PE/DotNet/Metadata/Tables/RedirectedMetadataRange.cs diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/ContinuousMetadataRange.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/ContinuousMetadataRange.cs deleted file mode 100644 index 84128c52c..000000000 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/ContinuousMetadataRange.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace AsmResolver.PE.DotNet.Metadata.Tables -{ - /// - /// Represents a simple range of metadata tokens that is continuous from the start to the end of the range. - /// - public class ContinuousMetadataRange : MetadataRange - { - /// - /// Creates a new continuous metadata range, indicating the table, the start- and end row within the table. - /// - /// The table. - /// The starting row identifier. - /// The ending row identifier. This identifier is exclusive. - public ContinuousMetadataRange(TableIndex table, uint startRid, uint endRid) - : base(table, startRid, endRid) - { - } - - /// - public override IEnumerator GetEnumerator() - { - return new Enumerator(this); - } - - /// - /// Provides an implementation of an enumerator for a continuous metadata range. - /// - public struct Enumerator : IEnumerator - { - private readonly ContinuousMetadataRange _range; - private uint _currentRid; - - /// - /// Creates a new enumerator for the provided continuous range. - /// - /// The range. - public Enumerator(ContinuousMetadataRange range) - { - _range = range; - _currentRid = _range.StartRid - 1; - } - - /// - public MetadataToken Current => new(_range.Table, _currentRid); - - object IEnumerator.Current => Current; - - /// - public bool MoveNext() - { - if (_currentRid < _range.EndRid - 1) - { - _currentRid++; - return true; - } - - return false; - } - - /// - public void Reset() - { - _currentRid = _range.StartRid - 1; - } - - void IDisposable.Dispose() - { - } - } - } -} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs index 59b6520f6..0b2352cb4 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs @@ -53,12 +53,21 @@ bool IsSorted /// The row. IMetadataRow GetByRid(uint rid); + /// + /// Attempts to get the contents of a cell in the table by its row identifier and column index. + /// + /// The row identifier. + /// The column index. + /// When successful, the contents of the cell, converted to an unsigned integer. + /// true if the cell existed and was obtained successfully, false otherwise. + bool TryGetCell(uint rid, int column, out uint value); + /// /// Attempts to get the contents of a row by its row identifier. /// /// The row identifier. /// When successful, the read row. - /// true if the RID existed an the row was obtained successfully, false otherwise. + /// true if the RID existed and the row was obtained successfully, false otherwise. bool TryGetByRid(uint rid, out IMetadataRow row); /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs index 16116f60f..6dad92aa4 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs @@ -1,39 +1,56 @@ using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace AsmResolver.PE.DotNet.Metadata.Tables { /// /// Represents a range of metadata tokens, indicated by a starting and ending row identifier within a metadata table. /// - public abstract class MetadataRange : IEnumerable + public readonly struct MetadataRange : IEnumerable { /// /// Represents the empty metadata range. /// - public static readonly MetadataRange Empty = new ContinuousMetadataRange(TableIndex.Module, 1, 1); - + public static readonly MetadataRange Empty = new(TableIndex.Module, 1, 1); + /// /// Initializes the range. /// /// The table. /// The starting row identifier. /// The ending row identifier. This identifier is exclusive. - protected MetadataRange(TableIndex table, uint startRid, uint endRid) + public MetadataRange(TableIndex table, uint startRid, uint endRid) { Table = table; StartRid = startRid; EndRid = endRid; + RedirectionTable = null; } - + /// - /// Gets the index of the metadata table this range is targeting. + /// Initializes the range. + /// + /// The table that is used for translating raw indices. + /// The table. + /// The starting row identifier. + /// The ending row identifier. This identifier is exclusive. + public MetadataRange(IMetadataTable redirectionTable, TableIndex table, uint startRid, uint endRid) + { + Table = table; + StartRid = startRid; + EndRid = endRid; + RedirectionTable = redirectionTable; + } + + /// + /// Gets the index of the metadata table this range is targeting. /// public TableIndex Table { get; } - + /// /// Gets the first row identifier that this range includes. /// @@ -43,24 +60,112 @@ public uint StartRid } /// - /// Gets the row identifier indicating the end of the range. The range excludes this row identifier. + /// Gets the row identifier indicating the end of the range. The range excludes this row identifier. /// public uint EndRid { get; } + /// + /// Gets a value indicating whether the range is empty or not. + /// + public bool IsEmpty => EndRid == StartRid; + + /// + /// Gets the table that is used for translating raw indices. + /// + public IMetadataTable? RedirectionTable + { + get; + } + + /// + /// Gets a value indicating whether the range is associated to a redirection table. + /// + [MemberNotNullWhen(true, nameof(RedirectionTable))] + public bool IsRedirected => RedirectionTable is not null; + /// /// Gets the number of metadata rows this range spans. /// public int Count => (int) (EndRid - StartRid); + /// + /// Obtains an enumerator that enumerates all metadata tokens within the range. + /// + /// + public Enumerator GetEnumerator() => new(this); + /// - public abstract IEnumerator GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() + /// + public override string ToString() { - return GetEnumerator(); + var start = new MetadataToken(Table, StartRid); + var end = new MetadataToken(Table, EndRid); + return $"[0x{start.ToString()}..0x{end.ToString()})"; + } + + /// + /// Represents an enumerator that enumerates all metadata tokens within a token range. + /// + public struct Enumerator : IEnumerator + { + private readonly MetadataRange _range; + private uint _currentRid; + + /// + /// Initializes a new token enumerator. + /// + /// The range to enumerate from. + public Enumerator(MetadataRange range) + { + _range = range; + _currentRid = range.StartRid - 1; + } + + /// + public MetadataToken Current + { + get + { + uint actualRid; + + if (!_range.IsRedirected) + actualRid = _currentRid; + else + _range.RedirectionTable.TryGetCell(_currentRid, 0, out actualRid); + + return new MetadataToken(_range.Table, actualRid); + } + } + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() + { + if (_currentRid < _range.EndRid - 1) + { + _currentRid++; + return true; + } + + return false; + } + + /// + public void Reset() => _currentRid = 0; + + /// + public void Dispose() + { + } } } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs index aaaea27c0..724d69d37 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs @@ -180,6 +180,19 @@ public int Capacity /// The row. public TRow GetByRid(uint rid) => this[(int) (rid - 1)]; + /// + public bool TryGetCell(uint rid, int column, out uint value) + { + if (column >= 0 && column < Layout.Columns.Count && TryGetByRid(rid, out var row)) + { + value = row[column]; + return true; + } + + value = 0; + return false; + } + IMetadataRow IMetadataTable.GetByRid(uint rid) => GetByRid(rid); /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/RedirectedMetadataRange.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/RedirectedMetadataRange.cs deleted file mode 100644 index a8c2f4553..000000000 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/RedirectedMetadataRange.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace AsmResolver.PE.DotNet.Metadata.Tables -{ - /// - /// Provides an implementation of a metadata range that is adjusted by an redirect metadata table (such as the field, - /// method, event or property pointer table). - /// - public class RedirectedMetadataRange : MetadataRange - { - /// - /// Creates a new range of metadata tokens that is adjusted by a redirection table. - /// - /// The table providing the redirections. - /// The table. - /// The starting row identifier. - /// The ending row identifier. This identifier is exclusive. - public RedirectedMetadataRange(IMetadataTable indirectTable, TableIndex table, uint startRid, uint endRid) - : base(table, startRid, endRid) - { - IndirectTable = indirectTable; - } - - /// - /// Gets the table responsible for redirecting metadata tokens. - /// - public IMetadataTable IndirectTable - { - get; - } - - /// - public override IEnumerator GetEnumerator() - { - return new Enumerator(this); - } - - /// - /// Provides an implementation of an enumerator for a redirected metadata range. - /// - public struct Enumerator : IEnumerator - { - private readonly RedirectedMetadataRange _range; - private uint _currentRid; - - /// - /// Creates a new enumerator for the provided continuous range. - /// - /// The range. - public Enumerator(RedirectedMetadataRange range) - { - _range = range; - _currentRid = _range.StartRid - 1; - } - - /// - public MetadataToken Current - { - get - { - uint actualRid = _currentRid - 1 < _range.IndirectTable.Count - ? _range.IndirectTable[(int) (_currentRid - 1)][0] - : _currentRid - 1; - return new MetadataToken(_range.Table, actualRid); - } - } - - object IEnumerator.Current => Current; - - /// - public bool MoveNext() - { - if (_currentRid < _range.EndRid - 1) - { - _currentRid++; - return true; - } - - return false; - } - - /// - public void Reset() - { - _currentRid = _range.StartRid - 1; - } - - void IDisposable.Dispose() - { - } - } - } -} \ No newline at end of file diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index 8c6089ad1..67efc4806 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -610,10 +610,10 @@ protected virtual uint GetColumnSize(ColumnType columnType) // Check if redirect table is present. var redirectTable = GetTable(redirectTableIndex); if (redirectTable.Count > 0) - return new RedirectedMetadataRange(redirectTable, memberTableIndex, startRid, endRid); + return new MetadataRange(redirectTable, memberTableIndex, startRid, endRid); // If not, its a simple range. - return new ContinuousMetadataRange(memberTableIndex, startRid, endRid); + return new MetadataRange(memberTableIndex, startRid, endRid); } } } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/MetadataRangeTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/MetadataRangeTest.cs index 5b1b4ca65..6c8e6ed55 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/MetadataRangeTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/MetadataRangeTest.cs @@ -10,7 +10,7 @@ public class MetadataRangeTest [Fact] public void ContinuousRangeEmpty() { - var range = new ContinuousMetadataRange(TableIndex.Method, 3, 3); + var range = new MetadataRange(TableIndex.Method, 3, 3); Assert.Equal(0, range.Count); Assert.Empty(range); } @@ -18,7 +18,7 @@ public void ContinuousRangeEmpty() [Fact] public void ContinuousRangeSingleItem() { - var range = new ContinuousMetadataRange(TableIndex.Method, 3, 4); + var range = new MetadataRange(TableIndex.Method, 3, 4); Assert.Equal(1, range.Count); Assert.Single(range); Assert.Equal(new MetadataToken(TableIndex.Method, 3), range.First()); @@ -27,7 +27,7 @@ public void ContinuousRangeSingleItem() [Fact] public void ContinuousRangeMultipleItems() { - var range = new ContinuousMetadataRange(TableIndex.Method, 3, 10); + var range = new MetadataRange(TableIndex.Method, 3, 10); Assert.Equal(7, range.Count); Assert.Equal(new[] { @@ -46,12 +46,12 @@ public void RedirectedRangeEmpty() { var stream = new TablesStream(); var redirectTable = stream.GetTable(); - - var range = new RedirectedMetadataRange(redirectTable, TableIndex.Method, 3, 3); + + var range = new MetadataRange(redirectTable, TableIndex.Method, 3, 3); Assert.Equal(0, range.Count); Assert.Empty(range); } - + [Fact] public void RedirectedRangeSingleItem() { @@ -62,13 +62,13 @@ public void RedirectedRangeSingleItem() redirectTable.Add(new MethodPointerRow(5)); redirectTable.Add(new MethodPointerRow(4)); redirectTable.Add(new MethodPointerRow(3)); - - var range = new RedirectedMetadataRange(redirectTable, TableIndex.Method, 3, 4); + + var range = new MetadataRange(redirectTable, TableIndex.Method, 3, 4); Assert.Equal(1, range.Count); Assert.Single(range); Assert.Equal(new MetadataToken(TableIndex.Method, 5), range.First()); } - + [Fact] public void RedirectedRangeMultipleItems() { @@ -82,8 +82,8 @@ public void RedirectedRangeMultipleItems() redirectTable.Add(new MethodPointerRow(9)); redirectTable.Add(new MethodPointerRow(8)); redirectTable.Add(new MethodPointerRow(10)); - - var range = new RedirectedMetadataRange(redirectTable, TableIndex.Method, 3, 8); + + var range = new MetadataRange(redirectTable, TableIndex.Method, 3, 8); Assert.Equal(5, range.Count); Assert.Equal(new[] { @@ -96,4 +96,4 @@ public void RedirectedRangeMultipleItems() } } -} \ No newline at end of file +} From 751141ffa3120960b8ca7ae4d2001dde5bbdb0fe Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 17:23:39 +0200 Subject: [PATCH 163/182] Add equality members for MetadataRange. --- .../DotNet/Metadata/Tables/MetadataRange.cs | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs index 6dad92aa4..afd594473 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -7,7 +8,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Represents a range of metadata tokens, indicated by a starting and ending row identifier within a metadata table. /// - public readonly struct MetadataRange : IEnumerable + public readonly struct MetadataRange : IEnumerable, IEquatable { /// /// Represents the empty metadata range. @@ -67,6 +68,11 @@ public uint EndRid get; } + /// + /// Gets the number of metadata rows this range spans. + /// + public int Count => (int) (EndRid - StartRid); + /// /// Gets a value indicating whether the range is empty or not. /// @@ -86,11 +92,6 @@ public uint EndRid [MemberNotNullWhen(true, nameof(RedirectionTable))] public bool IsRedirected => RedirectionTable is not null; - /// - /// Gets the number of metadata rows this range spans. - /// - public int Count => (int) (EndRid - StartRid); - /// /// Obtains an enumerator that enumerates all metadata tokens within the range. /// @@ -110,6 +111,40 @@ public override string ToString() return $"[0x{start.ToString()}..0x{end.ToString()})"; } + /// + public bool Equals(MetadataRange other) + { + if (IsEmpty && other.IsEmpty) + return true; + + return Table == other.Table + && StartRid == other.StartRid + && EndRid == other.EndRid + && Equals(RedirectionTable, other.RedirectionTable); + } + + /// + public override bool Equals(object? obj) + { + return obj is MetadataRange other && Equals(other); + } + + /// + public override int GetHashCode() + { + if (IsEmpty) + return 0; + + unchecked + { + int hashCode = (int) Table; + hashCode = (hashCode * 397) ^ (int) StartRid; + hashCode = (hashCode * 397) ^ (int) EndRid; + hashCode = (hashCode * 397) ^ (RedirectionTable is not null ? RedirectionTable.GetHashCode() : 0); + return hashCode; + } + } + /// /// Represents an enumerator that enumerates all metadata tokens within a token range. /// From 1ff0a0ffaf70cd8f72e80650de184276add24148 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 17:35:31 +0200 Subject: [PATCH 164/182] Replace LINQ code with for loop in GetSectionContaining[Rva|Offset] --- src/AsmResolver.PE.File/PEFile.cs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index 56cf6999d..26bb6e1fe 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -216,8 +216,19 @@ public PESection GetSectionContainingOffset(ulong fileOffset) /// true if the section was found, false otherwise. public bool TryGetSectionContainingOffset(ulong fileOffset, [NotNullWhen(true)] out PESection? section) { - section = Sections.FirstOrDefault(s => s.ContainsFileOffset(fileOffset)); - return section != null; + var sections = Sections; + + for (int i = 0; i < sections.Count; i++) + { + if (sections[i].ContainsFileOffset(fileOffset)) + { + section = sections[i]; + return true; + } + } + + section = null; + return false; } /// @@ -231,8 +242,19 @@ public PESection GetSectionContainingRva(uint rva) /// public bool TryGetSectionContainingRva(uint rva, [NotNullWhen(true)] out PESection? section) { - section = Sections.FirstOrDefault(s => s.ContainsRva(rva)); - return section != null; + var sections = Sections; + + for (int i = 0; i < sections.Count; i++) + { + if (sections[i].ContainsRva(rva)) + { + section = sections[i]; + return true; + } + } + + section = null; + return false; } /// From 69a5196e5fad348ee65c2e228c46b3abee69d8ac Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 17:44:44 +0200 Subject: [PATCH 165/182] Initialize streamHeaders with empty array. --- src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs index 11ab40619..16c3080bc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs @@ -33,6 +33,7 @@ public SerializedMetadata(MetadataReaderContext context, ref BinaryStreamReader Rva = directoryReader.Rva; _streamContentsReader = directoryReader.Fork(); + _streamHeaders = Array.Empty(); // Verify signature. var signature = (MetadataSignature) directoryReader.ReadUInt32(); From 07b09c33cbc7a98d616e64c7baed834a767e20df Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 18:30:35 +0200 Subject: [PATCH 166/182] Replace ICollection with concrete type in OneToManyRelation. --- .../SerializedModuleDefinition.Metadata.cs | 14 +-- .../Collections/OneToManyRelation.cs | 85 +++++++++++++++++-- 2 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs index a8f34c45f..243f31ca5 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 ICollection GetNestedTypeRids(uint enclosingTypeRid) + internal OneToManyRelation.ValueSet GetNestedTypeRids(uint enclosingTypeRid) { EnsureTypeDefinitionTreeInitialized(); return _typeDefTree.GetValues(enclosingTypeRid); @@ -123,7 +123,7 @@ private void InitializeMethodSemantics() Interlocked.CompareExchange(ref _semanticMethods, semanticMethods, null); } - internal ICollection GetMethodSemantics(MetadataToken owner) + internal OneToManyRelation.ValueSet GetMethodSemantics(MetadataToken owner) { EnsureMethodSemanticsInitialized(); return _semantics.GetValues(owner); @@ -213,7 +213,7 @@ private void EnsureCustomAttributesInitialized() /// protected override IList GetCustomAttributes() => GetCustomAttributeCollection(this); - internal ICollection GetCustomAttributes(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetCustomAttributes(MetadataToken ownerToken) { EnsureCustomAttributesInitialized(); return _customAttributes.GetValues(ownerToken); @@ -315,7 +315,7 @@ internal MetadataToken GetGenericParameterOwner(uint parameterRid) return _genericParameters.GetKey(parameterRid); } - internal ICollection GetGenericParameters(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetGenericParameters(MetadataToken ownerToken) { EnsureGenericParametersInitialized(); return _genericParameters.GetValues(ownerToken); @@ -350,7 +350,7 @@ internal MetadataToken GetGenericParameterConstraintOwner(uint constraintRid) return _genericParameterConstraints.GetKey(constraintRid); } - internal ICollection GetGenericParameterConstraints(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetGenericParameterConstraints(MetadataToken ownerToken) { EnsureGenericParameterConstrainsInitialized(); return _genericParameterConstraints.GetValues(ownerToken); @@ -385,7 +385,7 @@ internal MetadataToken GetInterfaceImplementationOwner(uint implementationRid) return _interfaces.GetKey(implementationRid); } - internal ICollection GetInterfaceImplementationRids(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetInterfaceImplementationRids(MetadataToken ownerToken) { EnsureInterfacesInitialized(); return _interfaces.GetValues(ownerToken); @@ -414,7 +414,7 @@ private void EnsureMethodImplementationsInitialized() return methodImplementations; } - internal ICollection GetMethodImplementationRids(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetMethodImplementationRids(MetadataToken ownerToken) { EnsureMethodImplementationsInitialized(); return _methodImplementations.GetValues(ownerToken); diff --git a/src/AsmResolver/Collections/OneToManyRelation.cs b/src/AsmResolver/Collections/OneToManyRelation.cs index 30df87146..ce3e55a4a 100644 --- a/src/AsmResolver/Collections/OneToManyRelation.cs +++ b/src/AsmResolver/Collections/OneToManyRelation.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections; using System.Collections.Generic; namespace AsmResolver.Collections @@ -11,7 +13,7 @@ public sealed class OneToManyRelation where TKey : notnull where TValue : notnull { - private readonly Dictionary> _memberLists; + private readonly Dictionary _memberLists; private readonly Dictionary _memberOwners; /// @@ -19,7 +21,7 @@ public sealed class OneToManyRelation /// public OneToManyRelation() { - _memberLists = new Dictionary>(); + _memberLists = new Dictionary(); _memberOwners = new Dictionary(); } @@ -29,7 +31,7 @@ public OneToManyRelation() /// The initial number of elements the relation can store. public OneToManyRelation(int capacity) { - _memberLists = new Dictionary>(capacity); + _memberLists = new Dictionary(capacity); _memberOwners = new Dictionary(capacity); } @@ -44,7 +46,7 @@ public bool Add(TKey key, TValue value) { if (!_memberOwners.ContainsKey(value)) { - GetValues(key).Add(value); + GetValues(key).Items.Add(value); _memberOwners.Add(value, key); return true; } @@ -57,11 +59,11 @@ public bool Add(TKey key, TValue value) /// /// The key. /// The values. - public ICollection GetValues(TKey key) + public ValueSet GetValues(TKey key) { if (!_memberLists.TryGetValue(key, out var items)) { - items = new List(); + items = new ValueSet(); _memberLists.Add(key, items); } @@ -79,5 +81,76 @@ public ICollection GetValues(TKey key) ? key : default; } + + /// + /// Represents a collection of values assigned to a single key in a one-to-many relation. + /// + public class ValueSet : ICollection + { + internal List Items + { + get; + } = new(); + + /// + public int Count => Items.Count; + + /// + public bool IsReadOnly => true; + + /// + public void Add(TValue item) => throw new NotSupportedException(); + + /// + public void Clear() => throw new NotSupportedException(); + + /// + public bool Contains(TValue item) => Items.Contains(item); + + /// + public void CopyTo(TValue[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex); + + /// + public bool Remove(TValue item) => throw new NotSupportedException(); + + /// + /// Gets an enumerator that enumerates all values in the collection. + /// + public Enumerator GetEnumerator() => new(Items.GetEnumerator()); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) Items).GetEnumerator(); + + /// + /// Represents an enumerator that enumerates all items in a value collection. + /// + public struct Enumerator : IEnumerator + { + private List.Enumerator _enumerator; + + internal Enumerator(List.Enumerator enumerator) + { + _enumerator = enumerator; + } + + /// + public TValue Current => _enumerator.Current!; + + /// + object IEnumerator.Current => ((IEnumerator) _enumerator).Current!; + + /// + public bool MoveNext() => _enumerator.MoveNext(); + + /// + public void Reset() => throw new NotSupportedException(); + + /// + public void Dispose() => _enumerator.Dispose(); + } + } } } From f23314d0561f4cec583d751e8931145ca530d532 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 17 Sep 2022 18:40:45 +0200 Subject: [PATCH 167/182] Reduce enumerator allocations for various hot paths. --- src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs | 5 +++-- src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs | 5 ++++- .../Serialized/SerializedFieldDefinition.cs | 2 +- src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs | 4 ++-- src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs | 4 +++- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs index 30750b64a..ca626518d 100644 --- a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs +++ b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs @@ -188,8 +188,9 @@ private void CollectNewlyAddedFixedMembers() var method = type.Methods[i]; InsertOrAppendIfNew(method, true); - foreach (var parameter in method.ParameterDefinitions) - InsertOrAppendIfNew(parameter, true); + // Try find new parameters. + for (int j = 0; j < method.ParameterDefinitions.Count; j++) + InsertOrAppendIfNew(method.ParameterDefinitions[j], true); } // Try find new properties. diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs index 48026a8a6..3be2cfbfd 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs @@ -112,7 +112,10 @@ private CilRawMethodBody BuildFatMethodBody(MethodBodySerializationContext conte } else { - var localVarSig = new LocalVariablesSignature(body.LocalVariables.Select(v => v.VariableType)); + var localVarSig = new LocalVariablesSignature(); + for (int i = 0; i < body.LocalVariables.Count; i++) + localVarSig.VariableTypes.Add(body.LocalVariables[i].VariableType); + var standAloneSig = new StandAloneSignature(localVarSig); token = context.TokenProvider.GetStandAloneSignatureToken(standAloneSig); } diff --git a/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs index 4ee4fa366..083dd51e9 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs @@ -86,7 +86,7 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke uint rid = module.GetFieldRvaRid(MetadataToken); bool result = _context.TablesStream - .GetTable() + .GetTable(TableIndex.FieldRva) .TryGetByRid(rid, out var fieldRvaRow); return result diff --git a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs index 165e68558..068dfc4a9 100644 --- a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs @@ -100,8 +100,8 @@ protected override void WriteContents(in BlobSerializationContext context) writer.WriteByte((byte) Attributes); writer.WriteCompressedUInt32((uint) VariableTypes.Count); - foreach (var type in VariableTypes) - type.Write(context); + for (int i = 0; i < VariableTypes.Count; i++) + VariableTypes[i].Write(context); } /// diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs index 2d22d134a..bc70ed255 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs @@ -203,7 +203,9 @@ public override uint GetPhysicalSize() { ulong sectionsOffset = endOffset.Align(4); length += (uint) (sectionsOffset - endOffset); - length += (uint) ExtraSections.Sum(x => x.GetPhysicalSize()); + + for (int i = 0; i < ExtraSections.Count; i++) + length += ExtraSections[i].GetPhysicalSize(); } return length; From 89455078fdecb96f07b8296ae91a3216442b537a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 14:47:12 +0000 Subject: [PATCH 168/182] Bump Microsoft.NET.Test.Sdk from 17.3.1 to 17.3.2 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.1 to 17.3.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.1...v17.3.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../AsmResolver.DotNet.Dynamic.Tests.csproj | 2 +- 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 +- .../AsmResolver.Symbols.Pdb.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj index 845faab14..456d762fe 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index d45a4dd3f..830749a8d 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 7e7716c88..6b532fdfc 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -9,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 82a56a966..3e76c6ef0 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 e9fb1eb4f..cec83bfea 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -8,7 +8,7 @@ - + all diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 3271b7040..582e228ed 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -8,7 +8,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 7bf34457f..6d9327ffb 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,7 +8,7 @@ - + all From 70269435564527aa0682ba0083b932ea4e6cac9c Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 5 Oct 2022 12:15:40 +0200 Subject: [PATCH 169/182] Add invalid method body tests. --- .../Code/Cil/CilMethodBodyTest.cs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs index 8d4953085..a3626ba04 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs @@ -617,5 +617,86 @@ public void ReadUserStringFromEnCMetadata() Assert.Equal("Hello World!", instruction.Operand); } + + private CilMethodBody CreateAndReadPatchedBody(IErrorListener listener, Action patch) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + // Add new dummy method to type. + var method = new MethodDefinition("Dummy", MethodAttributes.Static, + MethodSignature.CreateStatic(module.CorLibTypeFactory.Void)); + module.GetOrCreateModuleType().Methods.Add(method); + + // Give it a method body. + var body = new CilMethodBody(method); + method.MethodBody = body; + + // Add some random local variables. + for (int i = 0; i < 10; i++) + body.LocalVariables.Add(new CilLocalVariable(module.CorLibTypeFactory.Object)); + + // Add some random instructions. + for (int i = 0; i < 100; i++) + body.Instructions.Add(CilOpCodes.Nop); + body.Instructions.Add(CilOpCodes.Ret); + + // Construct PE image. + var result = new ManagedPEImageBuilder().CreateImage(module); + + // Look up raw method body. + var token = result.TokenMapping[method]; + var metadata = result.ConstructedImage!.DotNetDirectory!.Metadata!; + var rawBody = (CilRawFatMethodBody) metadata + .GetStream() + .GetTable() + .GetByRid(token.Rid) + .Body.GetSegment(); + + Assert.NotNull(rawBody); + + // Patch it. + patch(rawBody); + + // Read back module definition and look up interpreted method body. + module = ModuleDefinition.FromImage(result.ConstructedImage, new ModuleReaderParameters(listener)); + return ((MethodDefinition) module.LookupMember(token)).CilMethodBody; + } + + [Fact] + public void ReadLocalsFromBodyWithInvalidCodeStream() + { + var body = CreateAndReadPatchedBody(EmptyErrorListener.Instance, raw => + { + raw.Code = new DataSegment(new byte[] + { + 0xFE // 2-byte prefix opcode + }); + }); + + Assert.NotEmpty(body.LocalVariables); + } + + [Fact] + public void ReadCodeStreamFromBodyWithInvalidLocalVariablesSignature() + { + var body = CreateAndReadPatchedBody(EmptyErrorListener.Instance, raw => + { + raw.LocalVarSigToken = new MetadataToken(TableIndex.StandAloneSig, 0x123456); + }); + + Assert.NotEmpty(body.Instructions); + } + + [Fact] + public void ReadInvalidBody() + { + var body = CreateAndReadPatchedBody(EmptyErrorListener.Instance, raw => + { + raw.Code = new DataSegment(new byte[] { 0xFE }); + raw.LocalVarSigToken = new MetadataToken(TableIndex.StandAloneSig, 0x123456); + }); + + Assert.NotNull(body); + } } } From 448e39c9f15113d4b1347d428a00164593252112 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 5 Oct 2022 12:18:38 +0200 Subject: [PATCH 170/182] Forward CIL body reading errors to error listener in context. --- .../Code/Cil/CilMethodBody.cs | 81 +++++++++++++------ 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs index e1915c5e6..c706bcb48 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs @@ -147,15 +147,13 @@ public bool VerifyLabelsOnBuild { var result = new CilMethodBody(method); - operandResolver ??= new PhysicalCilOperandResolver(context.ParentModule, result); - // Interpret body header. var fatBody = rawBody as CilRawFatMethodBody; if (fatBody is not null) { result.MaxStack = fatBody.MaxStack; result.InitializeLocals = fatBody.InitLocals; - ReadLocalVariables(context.ParentModule, result, fatBody); + ReadLocalVariables(context, result, fatBody); } else { @@ -164,56 +162,89 @@ public bool VerifyLabelsOnBuild } // Parse instructions. - ReadInstructions(result, operandResolver, rawBody); + operandResolver ??= new PhysicalCilOperandResolver(context.ParentModule, result); + ReadInstructions(context, result, operandResolver, rawBody); // Read exception handlers. if (fatBody is not null) - ReadExceptionHandlers(fatBody, result); + ReadExceptionHandlers(context, fatBody, result); return result; } private static void ReadLocalVariables( - ModuleDefinition module, + ModuleReaderContext context, CilMethodBody result, CilRawFatMethodBody fatBody) { - if (fatBody.LocalVarSigToken != MetadataToken.Zero - && module.TryLookupMember(fatBody.LocalVarSigToken, out var member) - && member is StandAloneSignature {Signature: LocalVariablesSignature localVariablesSignature}) + // Method bodies can have 0 tokens if there are no locals defined. + if (fatBody.LocalVarSigToken == MetadataToken.Zero) + return; + + // If there is a non-zero token however, it needs to point to a stand-alone signature with a + // local variable signature stored in it. + if (!context.ParentModule.TryLookupMember(fatBody.LocalVarSigToken, out var member) + || member is not StandAloneSignature { Signature: LocalVariablesSignature localVariablesSignature }) { - var variableTypes = localVariablesSignature.VariableTypes; - for (int i = 0; i < variableTypes.Count; i++) - result.LocalVariables.Add(new CilLocalVariable(variableTypes[i])); + context.BadImage($"Method body of {result.Owner.SafeToString()} contains an invalid local variable signature token."); + return; } + + // Copy over the local variable types from the signature into the method body. + var variableTypes = localVariablesSignature.VariableTypes; + for (int i = 0; i < variableTypes.Count; i++) + result.LocalVariables.Add(new CilLocalVariable(variableTypes[i])); } private static void ReadInstructions( + ModuleReaderContext context, CilMethodBody result, ICilOperandResolver operandResolver, CilRawMethodBody rawBody) { - var reader = rawBody.Code.CreateReader(); - var disassembler = new CilDisassembler(reader, operandResolver); - result.Instructions.AddRange(disassembler.ReadInstructions()); + try + { + var reader = rawBody.Code.CreateReader(); + var disassembler = new CilDisassembler(reader, operandResolver); + result.Instructions.AddRange(disassembler.ReadInstructions()); + } + catch (Exception ex) + { + context.RegisterException(new BadImageFormatException( + $"Method body of {result.Owner.SafeToString()} contains an invalid CIL code stream.", ex)); + } } - private static void ReadExceptionHandlers(CilRawFatMethodBody fatBody, CilMethodBody result) + private static void ReadExceptionHandlers( + ModuleReaderContext context, + CilRawFatMethodBody fatBody, + CilMethodBody result) { - for (int i = 0; i < fatBody.ExtraSections.Count; i++) + try { - var section = fatBody.ExtraSections[i]; - if (section.IsEHTable) + for (int i = 0; i < fatBody.ExtraSections.Count; i++) { - var reader = ByteArrayDataSource.CreateReader(section.Data); - uint size = section.IsFat - ? CilExceptionHandler.FatExceptionHandlerSize - : CilExceptionHandler.TinyExceptionHandlerSize; + var section = fatBody.ExtraSections[i]; + if (section.IsEHTable) + { + var reader = ByteArrayDataSource.CreateReader(section.Data); + uint size = section.IsFat + ? CilExceptionHandler.FatExceptionHandlerSize + : CilExceptionHandler.TinyExceptionHandlerSize; - while (reader.CanRead(size)) - result.ExceptionHandlers.Add(CilExceptionHandler.FromReader(result, ref reader, section.IsFat)); + while (reader.CanRead(size)) + { + var handler = CilExceptionHandler.FromReader(result, ref reader, section.IsFat); + result.ExceptionHandlers.Add(handler); + } + } } } + catch (Exception ex) + { + context.RegisterException(new BadImageFormatException( + $"Method body of {result.Owner.SafeToString()} contains invalid extra sections.", ex)); + } } /// From 88e21763c2432d04734d4ac5f85c47dbb956bd79 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 5 Oct 2022 12:22:42 +0200 Subject: [PATCH 171/182] BUGFIX: GetStringToken should support returning original metadata tokens. --- .../Code/Cil/CilOperandBuilder.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilOperandBuilder.cs b/src/AsmResolver.DotNet/Code/Cil/CilOperandBuilder.cs index c39f58992..92fe90c14 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilOperandBuilder.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilOperandBuilder.cs @@ -31,8 +31,7 @@ public int GetVariableIndex(object? operand) CilLocalVariable localVariable => localVariable.Index, byte raw => raw, ushort raw => raw, - _ => _errorListener.RegisterExceptionAndReturnDefault( - new NotSupportedException($"Invalid or unsupported variable operand ({operand.SafeToString()}).")) + _ => _errorListener.NotSupportedAndReturn($"Invalid or unsupported variable operand ({operand.SafeToString()}).") }; } @@ -44,8 +43,7 @@ public int GetArgumentIndex(object? operand) Parameter parameter => parameter.MethodSignatureIndex, byte raw => raw, ushort raw => raw, - _ => _errorListener.RegisterExceptionAndReturnDefault( - new NotSupportedException($"Invalid or unsupported argument operand ({operand.SafeToString()}).")) + _ => _errorListener.NotSupportedAndReturn($"Invalid or unsupported argument operand ({operand.SafeToString()}).") }; } @@ -55,9 +53,9 @@ public uint GetStringToken(object? operand) return operand switch { string value => 0x70000000 | _provider.GetUserStringIndex(value), + MetadataToken token => token.ToUInt32(), uint raw => raw, - _ => _errorListener.RegisterExceptionAndReturnDefault( - new NotSupportedException($"Invalid or unsupported string operand ({operand.SafeToString()}).")) + _ => _errorListener.NotSupportedAndReturn($"Invalid or unsupported string operand ({operand.SafeToString()}).") }; } @@ -69,8 +67,7 @@ public MetadataToken GetMemberToken(object? operand) IMetadataMember member => GetMemberToken(member), MetadataToken token => token, uint raw => raw, - _ => _errorListener.RegisterExceptionAndReturnDefault( - new NotSupportedException($"Invalid or unsupported member operand ({operand.SafeToString()}).")) + _ => _errorListener.NotSupportedAndReturn($"Invalid or unsupported member operand ({operand.SafeToString()}).") }; } From bc372d29de1a279c58297abfd8e7657bfdf83886 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 5 Oct 2022 12:43:45 +0200 Subject: [PATCH 172/182] Add OriginalMetadataTokenProvider. --- .../Code/Cil/OriginalMetadataTokenProvider.cs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs diff --git a/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs b/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs new file mode 100644 index 000000000..db8440077 --- /dev/null +++ b/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs @@ -0,0 +1,74 @@ +using AsmResolver.DotNet.Builder.Metadata; +using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.UserStrings; + +namespace AsmResolver.DotNet.Code.Cil +{ + /// + /// Provides an implementation for the interface that always returns the + /// original metadata token that was assigned to the provided metadata member or string. + /// + public class OriginalMetadataTokenProvider : IMetadataTokenProvider + { + private readonly ModuleDefinition? _module; + + /// + /// Creates a new token provider. + /// + /// + /// The module to pull the original tokens from, or null if no verification should be done on the + /// declaring module. + /// + public OriginalMetadataTokenProvider(ModuleDefinition? module) + { + _module = module; + } + + private MetadataToken GetToken(IMetadataMember member) + { + if (_module is not null && member is IModuleProvider provider && provider.Module == _module) + throw new MemberNotImportedException(member); + + return member.MetadataToken; + } + + /// + public MetadataToken GetTypeReferenceToken(TypeReference type) => GetToken(type); + + /// + public MetadataToken GetTypeDefinitionToken(TypeDefinition type) => GetToken(type); + + /// + public MetadataToken GetFieldDefinitionToken(FieldDefinition field) => GetToken(field); + + /// + public MetadataToken GetMethodDefinitionToken(MethodDefinition method) => GetToken(method); + + /// + public MetadataToken GetMemberReferenceToken(MemberReference member) => GetToken(member); + + /// + public MetadataToken GetStandAloneSignatureToken(StandAloneSignature signature) => GetToken(signature); + + /// + public MetadataToken GetAssemblyReferenceToken(AssemblyReference assembly) => GetToken(assembly); + + /// + public MetadataToken GetTypeSpecificationToken(TypeSpecification type) => GetToken(type); + + /// + public MetadataToken GetMethodSpecificationToken(MethodSpecification method) => GetToken(method); + + /// + public uint GetUserStringIndex(string value) + { + if (_module?.DotNetDirectory?.Metadata?.TryGetStream(out UserStringsStream? stream) ?? false) + { + if (stream.TryFindStringIndex(value, out uint offset)) + return offset; + } + + return 0; + } + } +} From cb40ed3634fd25db8962d3fa330c0ad51f4885ce Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 5 Oct 2022 12:55:26 +0200 Subject: [PATCH 173/182] BUGFIX: Use current index instead of offset, allow for raw branch deltas in maxstack calculation. --- .../Code/Cil/CilMaxStackCalculator.cs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMaxStackCalculator.cs b/src/AsmResolver.DotNet/Code/Cil/CilMaxStackCalculator.cs index e485fd1e9..3237c0914 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMaxStackCalculator.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMaxStackCalculator.cs @@ -132,14 +132,31 @@ private void ScheduleSuccessors(in StackState currentState) { case CilFlowControl.Branch: // Schedule branch target. - ScheduleLabel(instruction.Offset, (ICilLabel) instruction.Operand!, nextStackSize); + switch (instruction.Operand) + { + case sbyte delta: + ScheduleDelta(currentState.InstructionIndex, delta, nextStackSize); + break; + + case int delta: + ScheduleDelta(currentState.InstructionIndex, delta, nextStackSize); + break; + + case ICilLabel label: + ScheduleLabel(currentState.InstructionIndex, label, nextStackSize); + break; + + default: + throw new NotSupportedException( + $"Invalid or unsupported operand type at offset IL_{instruction.Offset:X4}."); + } break; case CilFlowControl.ConditionalBranch when instruction.OpCode.Code == CilCode.Switch: // Schedule all switch targets for processing. var targets = (IList) instruction.Operand!; for (int i = 0; i < targets.Count; i++) - ScheduleLabel(instruction.Offset, targets[i], nextStackSize); + ScheduleLabel(currentState.InstructionIndex, targets[i], nextStackSize); // Schedule default case (= fallthrough instruction). ScheduleNext(currentState.InstructionIndex, nextStackSize); @@ -147,7 +164,7 @@ private void ScheduleSuccessors(in StackState currentState) case CilFlowControl.ConditionalBranch: // Schedule branch target. - ScheduleLabel(instruction.Offset, (ICilLabel) instruction.Operand!, nextStackSize); + ScheduleLabel(currentState.InstructionIndex, (ICilLabel) instruction.Operand!, nextStackSize); // Schedule fallthrough instruction. ScheduleNext(currentState.InstructionIndex, nextStackSize); @@ -187,6 +204,14 @@ private void ScheduleLabel(int currentIndex, ICilLabel label, int nextStackSize) ScheduleIndex(currentIndex, nextIndex, label.Offset, nextStackSize); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ScheduleDelta(int currentIndex, int offsetDelta, int nextStackSize) + { + int nextOffset = _body.Instructions[currentIndex].Offset + offsetDelta; + int nextIndex = _body.Instructions.GetIndexByOffset(nextOffset); + ScheduleIndex(currentIndex, nextIndex, nextOffset, nextStackSize); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ScheduleNext(int currentIndex, int nextStackSize) { From 7cf40016dc15d087a2a130ebf4b02732270d6c1f Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 5 Oct 2022 12:56:58 +0200 Subject: [PATCH 174/182] Use RelativeOffset instead of manual offset calculation. --- src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs b/src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs index eab86fb08..92aab4cb6 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs @@ -162,7 +162,7 @@ private CilOpCode ReadOpCode() return _reader.ReadInt32(); case CilOperandType.InlineBrTarget: - return new CilOffsetLabel(_reader.ReadInt32() + (int) (_reader.Offset - _reader.StartOffset)); + return new CilOffsetLabel(_reader.ReadInt32() + (int) _reader.RelativeOffset); case CilOperandType.ShortInlineR: return _reader.ReadSingle(); From 52f2e6f2cada7dbb548cd928bf8d45fd7c444b44 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 8 Oct 2022 15:27:52 +0200 Subject: [PATCH 175/182] Allow native method body serializer to return arbitrary segments from non-nativemethodbody instances. --- src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs | 6 +++--- .../Code/Native/NativeMethodBodySerializer.cs | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs index 3be2cfbfd..43c98d62d 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs @@ -23,7 +23,7 @@ public class CilMethodBodySerializer : IMethodBodySerializer /// /// /// - /// When this property is set to true, the maximum stack depth of all method bodies will be recaculated. + /// When this property is set to true, the maximum stack depth of all method bodies will be recalculated. /// /// /// When this property is set to false, the maximum stack depth of all method bodies will be preserved. @@ -37,7 +37,7 @@ public class CilMethodBodySerializer : IMethodBodySerializer { get; set; - } + } = null; /// /// Gets or sets the value of an override switch indicating whether labels should always be verified for @@ -64,7 +64,7 @@ public class CilMethodBodySerializer : IMethodBodySerializer /// public ISegmentReference SerializeMethodBody(MethodBodySerializationContext context, MethodDefinition method) { - if (method.CilMethodBody == null) + if (method.CilMethodBody is null) return SegmentReference.Null; var body = method.CilMethodBody; diff --git a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs index aed8247ec..b1a8d9f37 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs @@ -12,6 +12,12 @@ public class NativeMethodBodySerializer : IMethodBodySerializer /// public ISegmentReference SerializeMethodBody(MethodBodySerializationContext context, MethodDefinition method) { + // We treat any non-conventional bounded method body as a plain native method body that we + // don't need to process further. + if (method.MethodBody is {Address.IsBounded: true} plainBody) + return plainBody.Address; + + // We only support special treatment of native method bodies. if (method.MethodBody is not NativeMethodBody nativeMethodBody) return SegmentReference.Null; From b0b13c8be4a81ed56d983b57589fc00a3913288e Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 8 Oct 2022 15:46:14 +0200 Subject: [PATCH 176/182] Ensure DynamicMethodDefinition can read from DynamicILInfo initialized methods. --- .../DynamicMethodDefinition.cs | 22 ++++++++++++++++--- .../DynamicMethodHelper.cs | 12 ++++++---- .../DynamicMethodDefinitionTest.cs | 17 ++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index 4349d9006..3cb1512b3 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -69,14 +69,30 @@ private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition modul /// The method body. private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, object dynamicMethodObj) { - if (!(method.Module is SerializedModuleDefinition module)) + if (method.Module is not SerializedModuleDefinition module) throw new ArgumentException("Method body should reference a serialized module."); var result = new CilMethodBody(method); dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); - //Get Runtime Fields - byte[] code = FieldReader.ReadField(dynamicMethodObj, "m_code")!; + // Attempt to get the code field. + byte[]? code = FieldReader.ReadField(dynamicMethodObj, "m_code"); + + // If it is still null, it might still be set using DynamicILInfo::SetCode. + // Find the code stored in the DynamicILInfo if available. + if (code is null + && FieldReader.TryReadField(dynamicMethodObj, "m_method", out var methodBase) + && methodBase is not null + && FieldReader.TryReadField(methodBase, "m_DynamicILInfo", out object? dynamicILInfo) + && dynamicILInfo is not null) + { + code = FieldReader.ReadField(dynamicILInfo, "m_code"); + } + + if (code is null) + throw new InvalidOperationException("Dynamic method does not have a CIL code stream."); + + // Get remaining fields. object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; var tokenList = FieldReader.ReadField>(scope, "m_tokens")!; byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!; diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index 118fd49ac..eb6ce85f2 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -108,7 +108,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter for (int i = 0; i < FieldReader.ReadField(ehInfo, "m_currentCatch"); i++) { // Get ExceptionHandlerInfo Field Values - var endFinally = FieldReader.ReadField(ehInfo, "m_endFinally"); + int endFinally = FieldReader.ReadField(ehInfo, "m_endFinally"); var instructions = methodBody.Instructions; var endFinallyLabel = endFinally >= 0 @@ -155,17 +155,21 @@ public static object ResolveDynamicResolver(object dynamicMethodObj) if (dynamicMethodObj.GetType().FullName == "System.Reflection.Emit.DynamicMethod") { - var resolver = FieldReader.ReadField(dynamicMethodObj, "m_resolver"); + object? resolver = FieldReader.ReadField(dynamicMethodObj, "m_resolver"); if (resolver != null) dynamicMethodObj = resolver; } //Create Resolver if it does not exist. if (dynamicMethodObj.GetType().FullName == "System.Reflection.Emit.DynamicMethod") { - var dynamicResolver = typeof(OpCode).Module.GetTypes() + var dynamicResolver = typeof(OpCode).Module + .GetTypes() .First(t => t.Name == "DynamicResolver"); - var ilGenerator = dynamicMethodObj.GetType().GetRuntimeMethods().First(q => q.Name == "GetILGenerator") + object? ilGenerator = dynamicMethodObj + .GetType() + .GetRuntimeMethods() + .First(q => q.Name == "GetILGenerator") .Invoke(dynamicMethodObj, null); //Create instance of dynamicResolver diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index 7d3f91e09..ee2dea17e 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -72,5 +73,21 @@ public void RtDynamicMethod() module.CorLibTypeFactory.String, }, dynamicMethod.CilMethodBody.LocalVariables.Select(v => v.VariableType)); } + + [Fact] + public void ReadDynamicMethodInitializedByDynamicILInfo() + { + var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); + var info = method.GetDynamicILInfo(); + info.SetLocalSignature(new byte[] { 0x7, 0x0 }); + info.SetCode(new byte[] {0x2a}, 1); + + var contextModule = ModuleDefinition.FromFile(typeof(DynamicMethodDefinitionTest).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, method); + + Assert.NotNull(definition.CilMethodBody); + var instruction = Assert.Single(definition.CilMethodBody.Instructions); + Assert.Equal(CilOpCodes.Ret, instruction.OpCode); + } } } From 5b43856bd2d227615485a796d417d6c27da16f80 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 8 Oct 2022 20:16:04 +0200 Subject: [PATCH 177/182] Add TypeDefinition.IsByRefLike --- src/AsmResolver.DotNet/TypeDefinition.cs | 8 ++++++++ .../AsmResolver.DotNet.Tests/TypeDefinitionTest.cs | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index faecf8fbd..b9f658544 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using AsmResolver.Collections; using AsmResolver.DotNet.Code.Cil; @@ -498,6 +499,13 @@ public bool IsDelegate IsValueType && this.HasCustomAttribute("System.Runtime.CompilerServices", nameof(ReadOnlyAttribute)); + /// + /// Determines whether the type is marked with the IsByRefLike attribute, indicating a ref struct definition. + /// + public bool IsByRefLike => + IsValueType + && this.HasCustomAttribute("System.Runtime.CompilerServices", "IsByRefLikeAttribute"); + /// /// Gets a collection of fields defined in the type. /// diff --git a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs index 56e684962..a30204992 100644 --- a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -585,5 +586,18 @@ public void AddTypeWithCorLibBaseTypeToAssemblyWithCorLibTypeReferenceInAttribut var reference = Assert.IsAssignableFrom(corlib.Object.Scope!.GetAssembly()); Assert.Same(module, reference.Module); } + + [Fact] + public void ReadIsByRefLike() + { + var resolver = new DotNetCoreAssemblyResolver(new Version(5, 0)); + var corLib = resolver.Resolve(KnownCorLibs.SystemPrivateCoreLib_v5_0_0_0); + + var intType = corLib.ManifestModule.TopLevelTypes.First(t => t.Name == "Int32"); + var spanType = corLib.ManifestModule.TopLevelTypes.First(t => t.Name == "Span`1"); + + Assert.False(intType.IsByRefLike); + Assert.True(spanType.IsByRefLike); + } } } From bd45c4dac02c701f9ff213e26840db141f5076e4 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 14 Oct 2022 19:00:58 +0200 Subject: [PATCH 178/182] Guard lazylist modification methods with locks. --- src/AsmResolver/Collections/LazyList.cs | 53 +++++++++++++++++-------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/AsmResolver/Collections/LazyList.cs b/src/AsmResolver/Collections/LazyList.cs index 139761acf..6a88b6193 100644 --- a/src/AsmResolver/Collections/LazyList.cs +++ b/src/AsmResolver/Collections/LazyList.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; namespace AsmResolver.Collections { @@ -12,6 +13,7 @@ namespace AsmResolver.Collections [DebuggerDisplay("Count = {" + nameof(Count) + "}")] public abstract class LazyList : IList { + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion); private readonly List _items; /// @@ -41,8 +43,11 @@ public LazyList(int capacity) } set { - EnsureIsInitialized(); - OnSetItem(index, value); + lock (_items) + { + EnsureIsInitialized(); + OnSetItem(index, value); + } } } @@ -104,7 +109,7 @@ private void EnsureIsInitialized() { if (!IsInitialized) { - lock (this) + lock (_items) { if (!IsInitialized) { @@ -128,8 +133,11 @@ private void EnsureIsInitialized() /// public void Clear() { - OnClearItems(); - IsInitialized = true; + lock (_items) + { + OnClearItems(); + IsInitialized = true; + } } /// @@ -149,11 +157,15 @@ public void CopyTo(TItem[] array, int arrayIndex) /// public bool Remove(TItem item) { - EnsureIsInitialized(); - int index = Items.IndexOf(item); - if (index == -1) - return false; - OnRemoveItem(index); + lock (_items) + { + EnsureIsInitialized(); + int index = Items.IndexOf(item); + if (index == -1) + return false; + OnRemoveItem(index); + } + return true; } @@ -167,8 +179,11 @@ public int IndexOf(TItem item) /// public void Insert(int index, TItem item) { - EnsureIsInitialized(); - OnInsertItem(index, item); + lock (_items) + { + EnsureIsInitialized(); + OnInsertItem(index, item); + } } /// @@ -178,15 +193,21 @@ public void Insert(int index, TItem item) /// The items to insert. private void InsertRange(int index, IEnumerable items) { - EnsureIsInitialized(); - OnInsertRange(index, items); + lock (_items) + { + EnsureIsInitialized(); + OnInsertRange(index, items); + } } /// public void RemoveAt(int index) { - EnsureIsInitialized(); - OnRemoveItem(index); + lock (_items) + { + EnsureIsInitialized(); + OnRemoveItem(index); + } } /// From ec3ea1fde516641437fa4c142a8277f7227a8e64 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 16 Oct 2022 11:50:17 +0200 Subject: [PATCH 179/182] Add failing import by reflection tests. --- .../DynamicMethodDefinitionTest.cs | 31 +++++++++++++++++++ .../ReferenceImporterTest.cs | 15 +++++++++ 2 files changed, 46 insertions(+) diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index ee2dea17e..c65bba058 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -1,11 +1,15 @@ using System; +using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Methods; using AsmResolver.PE.DotNet.Cil; using Xunit; +using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; namespace AsmResolver.DotNet.Dynamic.Tests { @@ -89,5 +93,32 @@ public void ReadDynamicMethodInitializedByDynamicILInfo() var instruction = Assert.Single(definition.CilMethodBody.Instructions); Assert.Equal(CilOpCodes.Ret, instruction.OpCode); } + + [Fact] + public void ImportNestedType() + { + // https://github.com/Washi1337/AsmResolver/issues/363 + + var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); + var cil = method.GetILGenerator(); + cil.Emit(OpCodes.Call, typeof(NestedClass).GetMethod(nameof(NestedClass.TestMethod))!); + cil.Emit(OpCodes.Ret); + + var contextModule = ModuleDefinition.FromFile(typeof(DynamicMethodDefinitionTest).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, method); + + Assert.NotNull(definition.CilMethodBody); + var reference = Assert.IsAssignableFrom(definition.CilMethodBody.Instructions[0].Operand); + var declaringType = reference.DeclaringType; + Assert.NotNull(declaringType); + Assert.Equal(nameof(NestedClass), declaringType.Name); + Assert.NotNull(declaringType.DeclaringType); + Assert.Equal(nameof(DynamicMethodDefinitionTest), declaringType.DeclaringType.Name); + } + + internal static class NestedClass + { + public static void TestMethod() => Console.WriteLine("TestMethod"); + } } } diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index d7c2cdc0d..8112872b0 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -5,6 +5,7 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Fields; +using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -137,6 +138,20 @@ public void ImportNestedTypeDefinitionShouldImportParentType() Assert.Equal(_module, reference.DeclaringType.Module); } + [Fact] + public void ImportNestedTypeViaReflectionShouldImportParentType() + { + var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); + var declaringType = module.TopLevelTypes.First(t => t.Name == nameof(TopLevelClass1)); + var nested = declaringType.NestedTypes.First(t => t.Name == nameof(TopLevelClass1.Nested1)); + + var result = _importer.ImportType(typeof(TopLevelClass1.Nested1)); + + Assert.Equal(nested, result, Comparer); + Assert.Equal(_module, result.Module); + Assert.Equal(_module, result.DeclaringType?.Module); + } + [Fact] public void ImportSimpleTypeFromReflectionShouldResultInTypeRef() { From 489b90232df6e0110ca842fd57b8e09287641c6b Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 16 Oct 2022 12:01:08 +0200 Subject: [PATCH 180/182] BUGFIX: Import nested types by reflection should import declaring type and set namespace to null. --- .../DynamicCilOperandResolver.cs | 2 +- .../DynamicMethodDefinition.cs | 10 +++++----- src/AsmResolver.DotNet/AssemblyReference.cs | 6 ++++++ src/AsmResolver.DotNet/ReferenceImporter.cs | 16 ++++++++++++---- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs index 6cfa15b76..c06f786cf 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs @@ -26,7 +26,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe { _tokens = tokens ?? throw new ArgumentNullException(nameof(tokens)); _readerContext = contextModule.ReaderContext; - _importer = new ReferenceImporter(contextModule); + _importer = contextModule.DefaultImporter; } /// diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index 3cb1512b3..75ec3360d 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -36,22 +36,22 @@ public class DynamicMethodDefinition : MethodDefinition Module = module; Name = methodBase.Name; Attributes = (MethodAttributes)methodBase.Attributes; - Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase, module)); + Signature = module.DefaultImporter.ImportMethodSignature(ResolveSig(methodBase, module)); CilMethodBody = CreateDynamicMethodBody(this, dynamicMethodObj); } private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition module) { - var imp = new ReferenceImporter(module); + var importer = module.DefaultImporter; var returnType = methodBase is MethodInfo info - ? imp.ImportTypeSignature(info.ReturnType) + ? importer.ImportTypeSignature(info.ReturnType) : module.CorLibTypeFactory.Void; var parameters = methodBase.GetParameters(); var parameterTypes = new TypeSignature[parameters.Length]; for (int i = 0; i < parameterTypes.Length; i++) - parameterTypes[i] = imp.ImportTypeSignature(parameters[i].ParameterType); + parameterTypes[i] = importer.ImportTypeSignature(parameters[i].ParameterType); return new MethodSignature( methodBase.IsStatic ? 0 : CallingConventionAttributes.HasThis, @@ -108,7 +108,7 @@ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, ob result.Instructions.AddRange(disassembler.ReadInstructions()); //Exception Handlers - DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, new ReferenceImporter(module)); + DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, module.DefaultImporter); return result; } diff --git a/src/AsmResolver.DotNet/AssemblyReference.cs b/src/AsmResolver.DotNet/AssemblyReference.cs index d81b2b9c9..f699d00fc 100644 --- a/src/AsmResolver.DotNet/AssemblyReference.cs +++ b/src/AsmResolver.DotNet/AssemblyReference.cs @@ -70,8 +70,14 @@ public AssemblyReference(AssemblyDescriptor descriptor) Version = descriptor.Version; Attributes = descriptor.Attributes; HasPublicKey = false; + PublicKeyOrToken = descriptor.GetPublicKeyToken(); + if (PublicKeyOrToken?.Length == 0) + PublicKeyOrToken = null; + Culture = descriptor.Culture; + if (Utf8String.IsNullOrEmpty(Culture)) + Culture = null; } /// diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 65f1061cd..eb27ec74e 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -315,10 +315,18 @@ public virtual TypeSignature ImportTypeSignature(Type type) if (corlibType != null) return corlibType; - var reference = new TypeReference(TargetModule, - ImportAssembly(new ReflectionAssemblyDescriptor(TargetModule, type.Assembly.GetName())), - type.Namespace, - type.Name); + TypeReference reference; + + if (type.IsNested) + { + var scope = (IResolutionScope) ImportType(type.DeclaringType!); + reference = new TypeReference(TargetModule, scope, null, type.Name); + } + else + { + var scope = ImportAssembly(new ReflectionAssemblyDescriptor(TargetModule, type.Assembly.GetName())); + reference = new TypeReference(TargetModule, scope, type.Namespace, type.Name); + } return new TypeDefOrRefSignature(reference, type.IsValueType); } From 8cf0f4f120ac415015638eabb26e0d5e02df7169 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Sun, 16 Oct 2022 12:53:36 -0400 Subject: [PATCH 181/182] Make SignatureComparer immutable and add a default instance --- .../AssemblyResolverBase.cs | 5 +-- .../DefaultMetadataResolver.cs | 5 +-- .../SignatureComparer.ResolutionScope.cs | 45 +++++++++---------- .../Signatures/SignatureComparer.cs | 29 ++++++++++++ .../Signatures/SignatureComparisonFlags.cs | 44 ++++++++++++++++++ 5 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 src/AsmResolver.DotNet/Signatures/SignatureComparisonFlags.cs diff --git a/src/AsmResolver.DotNet/AssemblyResolverBase.cs b/src/AsmResolver.DotNet/AssemblyResolverBase.cs index bd3fa1ac1..64d057c99 100644 --- a/src/AsmResolver.DotNet/AssemblyResolverBase.cs +++ b/src/AsmResolver.DotNet/AssemblyResolverBase.cs @@ -13,10 +13,7 @@ namespace AsmResolver.DotNet public abstract class AssemblyResolverBase : IAssemblyResolver { private static readonly string[] BinaryFileExtensions = {".dll", ".exe"}; - private static readonly SignatureComparer Comparer = new() - { - AcceptNewerAssemblyVersionNumbers = true - }; + private static readonly SignatureComparer Comparer = new(SignatureComparisonFlags.AcceptNewerVersions); private readonly Dictionary _cache = new(new SignatureComparer()); diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs index 18e57b9e5..fcfc69b8b 100644 --- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs +++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs @@ -13,10 +13,7 @@ namespace AsmResolver.DotNet public class DefaultMetadataResolver : IMetadataResolver { private readonly ConcurrentDictionary _typeCache; - private readonly SignatureComparer _comparer = new() - { - IgnoreAssemblyVersionNumbers = true - }; + private readonly SignatureComparer _comparer = new(SignatureComparisonFlags.VersionAgnostic); /// /// Creates a new metadata resolver. diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs index 5eedef741..31fdb7eac 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs @@ -8,35 +8,28 @@ public partial class SignatureComparer : IEqualityComparer, IEqualityComparer { - /// - /// Gets or sets a value indicating whether version numbers should be excluded in the comparison of two - /// assembly descriptors. - /// - public bool IgnoreAssemblyVersionNumbers + private bool IgnoreAssemblyVersionNumbers { - get; - set; + get + { + return (Flags & SignatureComparisonFlags.VersionAgnostic) == SignatureComparisonFlags.VersionAgnostic; + } } - /// - /// Gets or sets a value indicating whether the containing assembly of the second member to compare is - /// allowed to be a newer version than the containing assembly of the first member. - /// - /// - /// - /// If this property is set to true, then any member reference that is contained in a certain - /// assembly (e.g. with version 1.0.0.0), will be considered equal to a member reference with the - /// same name or signature contained in an assembly with a newer version (e.g. 1.1.0.0). When this - /// property is set to false, the exact version number must match instead. - /// - /// - /// This property is ignored if is true. - /// - /// - public bool AcceptNewerAssemblyVersionNumbers + private bool AcceptNewerAssemblyVersionNumbers { - get; - set; + get + { + return (Flags & SignatureComparisonFlags.AcceptNewerVersions) == SignatureComparisonFlags.AcceptNewerVersions; + } + } + + private bool AcceptOlderAssemblyVersionNumbers + { + get + { + return (Flags & SignatureComparisonFlags.AcceptOlderVersions) == SignatureComparisonFlags.AcceptOlderVersions; + } } /// @@ -80,6 +73,8 @@ public bool Equals(AssemblyDescriptor? x, AssemblyDescriptor? y) versionMatch = true; else if (AcceptNewerAssemblyVersionNumbers) versionMatch = x.Version <= y.Version; + else if (AcceptOlderAssemblyVersionNumbers) + versionMatch = x.Version >= y.Version; else versionMatch = x.Version == y.Version; diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs index 09b9843ff..c4a67b84d 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs @@ -9,6 +9,35 @@ public partial class SignatureComparer : IEqualityComparer { private const int ElementTypeOffset = 24; + private const SignatureComparisonFlags DefaultFlags = SignatureComparisonFlags.ExactVersion; + + /// + /// An immutable default instance of . + /// + public static SignatureComparer Default { get; } = new(); + + /// + /// Flags for controlling comparison behavior. + /// + public SignatureComparisonFlags Flags { get; } + + /// + /// The default constructor. + /// + public SignatureComparer() + { + Flags = DefaultFlags; + } + + /// + /// A constructor with a parameter for specifying the + /// used in comparisons. + /// + /// The used in comparisons. + public SignatureComparer(SignatureComparisonFlags flags) + { + Flags = flags; + } /// public bool Equals(byte[]? x, byte[]? y) => ByteArrayEqualityComparer.Instance.Equals(x, y); diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparisonFlags.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparisonFlags.cs new file mode 100644 index 000000000..698e835cd --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparisonFlags.cs @@ -0,0 +1,44 @@ +using System; + +namespace AsmResolver.DotNet.Signatures +{ + /// + /// Flags for controlling the behavior of . + /// + [Flags] + public enum SignatureComparisonFlags + { + /// + /// When neither nor are specified, + /// the exact version number must match in the comparison of two assembly descriptors. + /// + ExactVersion = 0, + /// + /// If this flag is used, the containing assembly of the second member to compare is + /// allowed to be an older version than the containing assembly of the first member. + /// + /// + /// If this flag is used, then any member reference that is contained in a certain + /// assembly (e.g. with version 1.1.0.0), will be considered equal to a member reference with the + /// same name or signature contained in an assembly with a older version (e.g. 1.0.0.0). + /// Otherwise, they will be treated as inequal. + /// + AcceptOlderVersions = 1, + /// + /// If this flag is used, the containing assembly of the second member to compare is + /// allowed to be a newer version than the containing assembly of the first member. + /// + /// + /// If this flag is used, then any member reference that is contained in a certain + /// assembly (e.g. with version 1.0.0.0), will be considered equal to a member reference with the + /// same name or signature contained in an assembly with a newer version (e.g. 1.1.0.0). + /// Otherwise, they will be treated as inequal. + /// + AcceptNewerVersions = 2, + /// + /// If this flag is used, version numbers will be excluded in the comparison of two + /// assembly descriptors. + /// + VersionAgnostic = AcceptOlderVersions | AcceptNewerVersions, + } +} From 54c65ee64277b42326aec54f499efc7407a4e556 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 18 Oct 2022 20:58:06 +0200 Subject: [PATCH 182/182] Add BinaryStreamReader convenience constructors. Deprecate ByteArrayDataSource.CreateReader. --- appveyor.yml | 2 +- .../DynamicCilOperandResolver.cs | 2 +- .../DynamicMethodDefinition.cs | 2 +- .../DynamicMethodHelper.cs | 9 +++---- .../Code/Cil/CilMethodBody.cs | 2 +- .../Signatures/DataBlobSignature.cs | 2 +- src/AsmResolver.PE.File/PEFile.cs | 2 +- .../Metadata/Blob/SerializedBlobStream.cs | 4 +-- .../Metadata/Guid/SerializedGuidStream.cs | 4 +-- .../DotNet/Metadata/Metadata.cs | 2 +- .../Metadata/Pdb/SerializedPdbStream.cs | 4 +-- .../Strings/SerializedStringsStream.cs | 4 +-- .../Metadata/Tables/SerializedTableStream.cs | 2 +- .../SerializedUserStringsStream.cs | 4 +-- .../DotNet/StrongName/StrongNamePrivateKey.cs | 2 +- .../DotNet/StrongName/StrongNamePublicKey.cs | 2 +- src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs | 2 +- src/AsmResolver/IO/BinaryStreamReader.cs | 18 +++++++++++++ src/AsmResolver/IO/ByteArrayDataSource.cs | 4 +-- .../AssemblyDefinitionTest.cs | 2 +- .../Bundles/BundleManifestTest.cs | 2 +- .../CustomAttributeTest.cs | 2 +- .../ModuleDefinitionTest.cs | 4 +-- .../Resources/ResourceSetTest.cs | 6 ++--- .../SecurityDeclarationTest.cs | 2 +- .../DotNet/Metadata/MetadataTest.cs | 2 +- .../Metadata/Tables/Rows/RowTestUtils.cs | 4 +-- .../StrongName/StrongNamePublicKeyTest.cs | 2 +- .../Version/VersionInfoResourceTest.cs | 6 ++--- .../Metadata/Dbi/DbiStreamTest.cs | 2 +- .../Metadata/Info/InfoStreamTest.cs | 2 +- .../IO/BinaryStreamReaderTest.cs | 26 +++++++++---------- .../IO/BinaryStreamWriterTest.cs | 2 +- 33 files changed, 77 insertions(+), 60 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6c4e999bc..7a1ff9bec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 4.11.2-master-build.{build} + version: 5.0.0-master-build.{build} configuration: Release skip_commits: diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs index c06f786cf..3efbe83d1 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs @@ -95,7 +95,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe break; case TableIndex.StandAloneSig: - var reader = ByteArrayDataSource.CreateReader((byte[])_tokens[(int)token.Rid]!); + var reader = new BinaryStreamReader((byte[])_tokens[(int)token.Rid]!); return CallingConventionSignature.FromReader(new BlobReadContext(_readerContext), ref reader); } diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index 75ec3360d..e39ebc5f5 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -103,7 +103,7 @@ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, ob DynamicMethodHelper.ReadLocalVariables(result, method, localSig); // Read raw instructions. - var reader = ByteArrayDataSource.CreateReader(code); + var reader = new BinaryStreamReader(code); var disassembler = new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList)); result.Instructions.AddRange(disassembler.ReadInstructions()); diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index eb6ce85f2..9deb9ec5e 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -30,13 +30,12 @@ static DynamicMethodHelper() public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition method, byte[] localSig) { - if (!(method.Module is SerializedModuleDefinition module)) + if (method.Module is not SerializedModuleDefinition module) throw new ArgumentException("Method body should reference a serialized module."); - var reader = ByteArrayDataSource.CreateReader(localSig); - if (ReadLocalVariableSignature( - new BlobReadContext(module.ReaderContext), - ref reader) is not LocalVariablesSignature localsSignature) + var reader = new BinaryStreamReader(localSig); + if (ReadLocalVariableSignature(new BlobReadContext(module.ReaderContext), ref reader) + is not { } localsSignature) { throw new ArgumentException("Invalid local variables signature."); } diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs index c706bcb48..3e7beb925 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs @@ -227,7 +227,7 @@ public bool VerifyLabelsOnBuild var section = fatBody.ExtraSections[i]; if (section.IsEHTable) { - var reader = ByteArrayDataSource.CreateReader(section.Data); + var reader = new BinaryStreamReader(section.Data); uint size = section.IsFat ? CilExceptionHandler.FatExceptionHandlerSize : CilExceptionHandler.TinyExceptionHandlerSize; diff --git a/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs b/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs index 8dee782cd..d0c7d498d 100644 --- a/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs @@ -43,7 +43,7 @@ public byte[] Data /// The deserialized literal. public object InterpretData(ElementType elementType) { - var reader = ByteArrayDataSource.CreateReader(Data); + var reader = new BinaryStreamReader(Data); return elementType switch { diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index 26bb6e1fe..d1483258b 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -135,7 +135,7 @@ public static PEFile FromFile(IInputFile file) /// The raw bytes representing the contents of the PE file to read. /// The PE file that was read. /// Occurs when the file does not follow the PE file format. - public static PEFile FromBytes(byte[] raw) => FromReader(ByteArrayDataSource.CreateReader(raw)); + public static PEFile FromBytes(byte[] raw) => FromReader(new BinaryStreamReader(raw)); /// /// Reads a mapped PE file starting at the provided module base address (HINSTANCE). diff --git a/src/AsmResolver.PE/DotNet/Metadata/Blob/SerializedBlobStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Blob/SerializedBlobStream.cs index fbb895be8..03a74685d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Blob/SerializedBlobStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Blob/SerializedBlobStream.cs @@ -15,7 +15,7 @@ public class SerializedBlobStream : BlobStream /// /// The raw contents of the stream. public SerializedBlobStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -25,7 +25,7 @@ public SerializedBlobStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedBlobStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Guid/SerializedGuidStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Guid/SerializedGuidStream.cs index d03887e62..6634ecd83 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Guid/SerializedGuidStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Guid/SerializedGuidStream.cs @@ -18,7 +18,7 @@ public class SerializedGuidStream : GuidStream /// /// The raw contents of the stream. public SerializedGuidStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -28,7 +28,7 @@ public SerializedGuidStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedGuidStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs index a7429495b..0066a4ce3 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs @@ -81,7 +81,7 @@ public IList Streams /// /// The raw data. /// The read metadata. - public static Metadata FromBytes(byte[] data) => FromReader(ByteArrayDataSource.CreateReader(data)); + public static Metadata FromBytes(byte[] data) => FromReader(new BinaryStreamReader(data)); /// /// Reads a .NET metadata directory from a file. diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs index d6603f0b8..9a04fc7ac 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs @@ -14,7 +14,7 @@ public class SerializedPdbStream : PdbStream /// /// The raw contents of the stream. public SerializedPdbStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -24,7 +24,7 @@ public SerializedPdbStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedPdbStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs index 293562f36..ea17a11a1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs @@ -17,7 +17,7 @@ public class SerializedStringsStream : StringsStream /// /// The raw contents of the stream. public SerializedStringsStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -27,7 +27,7 @@ public SerializedStringsStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedStringsStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs index 8608952fc..becb9201e 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs @@ -38,7 +38,7 @@ public class SerializedTableStream : TablesStream, ILazyMetadataStream /// The name of the stream. /// The raw contents of the stream. public SerializedTableStream(MetadataReaderContext context, string name, byte[] rawData) - : this(context, name, ByteArrayDataSource.CreateReader(rawData)) + : this(context, name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs index b50077898..b86bdafb8 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs @@ -17,7 +17,7 @@ public class SerializedUserStringsStream : UserStringsStream /// /// The raw contents of the stream. public SerializedUserStringsStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -27,7 +27,7 @@ public SerializedUserStringsStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedUserStringsStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs index dc517f811..d004e5059 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs @@ -22,7 +22,7 @@ public class StrongNamePrivateKey : StrongNamePublicKey /// Occurs when an invalid or unsupported algorithm is specified. public new static StrongNamePrivateKey FromFile(string path) { - var reader = ByteArrayDataSource.CreateReader(System.IO.File.ReadAllBytes(path)); + var reader = new BinaryStreamReader(System.IO.File.ReadAllBytes(path)); return FromReader(ref reader); } diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs index ae410cbb8..cc47acef3 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs @@ -24,7 +24,7 @@ public class StrongNamePublicKey : StrongNameKeyStructure /// Occurs when an invalid or unsupported algorithm is specified. public static StrongNamePublicKey FromFile(string path) { - var reader = ByteArrayDataSource.CreateReader(System.IO.File.ReadAllBytes(path)); + var reader = new BinaryStreamReader(System.IO.File.ReadAllBytes(path)); return FromReader(ref reader); } diff --git a/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs index 16aff99f9..a58775dd5 100644 --- a/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs @@ -88,7 +88,7 @@ public MsfFile(uint blockSize) /// /// The data to interpret. /// The read MSF file. - public static MsfFile FromBytes(byte[] data) => FromReader(ByteArrayDataSource.CreateReader(data)); + public static MsfFile FromBytes(byte[] data) => FromReader(new BinaryStreamReader(data)); /// /// Reads an MSF file from the provided input stream reader. diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index 9034b714b..9a924cda9 100644 --- a/src/AsmResolver/IO/BinaryStreamReader.cs +++ b/src/AsmResolver/IO/BinaryStreamReader.cs @@ -13,6 +13,24 @@ public struct BinaryStreamReader [ThreadStatic] private static int[]? _buffer; + /// + /// Creates a new binary stream reader on the provided data source. + /// + /// The data to read from. + public BinaryStreamReader(byte[] data) + : this(new ByteArrayDataSource(data)) + { + } + + /// + /// Creates a new binary stream reader on the provided data source. + /// + /// The object to get the data from. + public BinaryStreamReader(IDataSource dataSource) + : this(dataSource, 0, 0, (uint) dataSource.Length) + { + } + /// /// Creates a new binary stream reader on the provided data source. /// diff --git a/src/AsmResolver/IO/ByteArrayDataSource.cs b/src/AsmResolver/IO/ByteArrayDataSource.cs index 7520fc8b3..c9dc3d8dc 100644 --- a/src/AsmResolver/IO/ByteArrayDataSource.cs +++ b/src/AsmResolver/IO/ByteArrayDataSource.cs @@ -46,8 +46,8 @@ public ulong BaseAddress /// /// The byte array to read. /// The stream reader. - public static BinaryStreamReader CreateReader(byte[] data) => - new(new ByteArrayDataSource(data), 0, 0, (uint) data.Length); + [Obsolete("Use the constructor of AsmResolver.IO.BinaryStreamReader instead.")] + public static BinaryStreamReader CreateReader(byte[] data) => new(data); /// public bool IsValidAddress(ulong address) => address - BaseAddress < (ulong) _data.Length; diff --git a/test/AsmResolver.DotNet.Tests/AssemblyDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/AssemblyDefinitionTest.cs index 0059ec8a3..120fa3635 100644 --- a/test/AsmResolver.DotNet.Tests/AssemblyDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/AssemblyDefinitionTest.cs @@ -13,7 +13,7 @@ private static AssemblyDefinition Rebuild(AssemblyDefinition assembly) { using var stream = new MemoryStream(); assembly.ManifestModule.Write(stream); - return AssemblyDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + return AssemblyDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); } [Fact] diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index e97dd755b..ffc339182 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -103,7 +103,7 @@ public void MarkFilesAsCompressed() using var stream = new MemoryStream(); ulong address = manifest.WriteManifest(new BinaryStreamWriter(stream), false); - var reader = ByteArrayDataSource.CreateReader(stream.ToArray()); + var reader = new BinaryStreamReader(stream.ToArray()); reader.Offset = address; var newManifest = BundleManifest.FromReader(reader); AssertBundlesAreEqual(manifest, newManifest); diff --git a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs index 1e65acde3..b05032774 100644 --- a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs +++ b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs @@ -34,7 +34,7 @@ public void PersistentConstructor() using var stream = new MemoryStream(); module.Write(stream); - module = ModuleDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + module = ModuleDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); Assert.All(type.CustomAttributes, a => diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index e6eed8351..7e2e1ba10 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -24,7 +24,7 @@ private static ModuleDefinition Rebuild(ModuleDefinition module) { using var stream = new MemoryStream(); module.Write(stream); - return ModuleDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + return ModuleDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); } [SkippableFact] @@ -285,7 +285,7 @@ public void PersistentResources() // Write and rebuild. using var stream = new MemoryStream(); module.Write(stream); - var newModule = ModuleDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + var newModule = ModuleDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); // Assert contents. var newDirectory = (IResourceDirectory)newModule.NativeResourceDirectory.Entries diff --git a/test/AsmResolver.DotNet.Tests/Resources/ResourceSetTest.cs b/test/AsmResolver.DotNet.Tests/Resources/ResourceSetTest.cs index 356c2113b..1d01b5fa5 100644 --- a/test/AsmResolver.DotNet.Tests/Resources/ResourceSetTest.cs +++ b/test/AsmResolver.DotNet.Tests/Resources/ResourceSetTest.cs @@ -87,7 +87,7 @@ public void PersistentIntrinsicElement(string key, ResourceTypeCode type, object using var stream = new MemoryStream(); set.Write(new BinaryStreamWriter(stream)); - var actualSet = ResourceSet.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + var actualSet = ResourceSet.FromReader(new BinaryStreamReader(stream.ToArray())); var actualEntry = actualSet.First(e => e.Name == key); Assert.Equal(entry.Type, actualEntry.Type); Assert.Equal(entry.Data, actualEntry.Data); @@ -110,7 +110,7 @@ public void PersistentUserDefinedTypeElement() using var stream = new MemoryStream(); set.Write(new BinaryStreamWriter(stream)); - var actualSet = ResourceSet.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + var actualSet = ResourceSet.FromReader(new BinaryStreamReader(stream.ToArray())); var actualEntry = actualSet.First(e => e.Name == "Point"); Assert.Equal(entry.Type.FullName, actualEntry.Type.FullName); Assert.Equal(entry.Data, actualEntry.Data); @@ -134,7 +134,7 @@ public void PersistentSetMultipleEntries() var resourceReader = new ResourceReader(stream); - var actualSet = ResourceSet.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + var actualSet = ResourceSet.FromReader(new BinaryStreamReader(stream.ToArray())); Assert.Equal(set.Count, actualSet.Count); for (int i = 0; i < set.Count; i++) { diff --git a/test/AsmResolver.DotNet.Tests/SecurityDeclarationTest.cs b/test/AsmResolver.DotNet.Tests/SecurityDeclarationTest.cs index c37960f59..36edb87c7 100644 --- a/test/AsmResolver.DotNet.Tests/SecurityDeclarationTest.cs +++ b/test/AsmResolver.DotNet.Tests/SecurityDeclarationTest.cs @@ -16,7 +16,7 @@ private static MethodDefinition LookupMethod(string methodName, bool rebuild) { var stream = new MemoryStream(); module.Write(stream); - module = ModuleDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + module = ModuleDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); } var type = module.TopLevelTypes.First(t => t.Name == nameof(SecurityAttributes)); diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs index bdfe57d9e..dc4a0740d 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs @@ -103,7 +103,7 @@ public void PreserveMetadataNoChange() using var tempStream = new MemoryStream(); metadata.Write(new BinaryStreamWriter(tempStream)); - var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var reader = new BinaryStreamReader(tempStream.ToArray()); var context = MetadataReaderContext.FromReaderContext(new PEReaderContext(peFile)); var newMetadata = new SerializedMetadata(context, ref reader); diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs index 8e4bcb459..99cf719bf 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs @@ -21,7 +21,7 @@ internal static class RowTestUtils using var tempStream = new MemoryStream(); expected.Write(new BinaryStreamWriter(tempStream), table.Layout); - var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var reader = new BinaryStreamReader(tempStream.ToArray()); var newRow = readRow(ref reader, table.Layout); Assert.Equal(expected, newRow); @@ -36,7 +36,7 @@ internal static class RowTestUtils using var tempStream = new MemoryStream(); expected.Write(new BinaryStreamWriter(tempStream), table.Layout); - var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var reader = new BinaryStreamReader(tempStream.ToArray()); var newRow = readRow(new MetadataReaderContext(VirtualAddressFactory.Instance), ref reader, table.Layout); Assert.Equal(expected, newRow); diff --git a/test/AsmResolver.PE.Tests/DotNet/StrongName/StrongNamePublicKeyTest.cs b/test/AsmResolver.PE.Tests/DotNet/StrongName/StrongNamePublicKeyTest.cs index 82ef37be1..ab8525f94 100644 --- a/test/AsmResolver.PE.Tests/DotNet/StrongName/StrongNamePublicKeyTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/StrongName/StrongNamePublicKeyTest.cs @@ -18,7 +18,7 @@ public void PersistentStrongNamePublicKey() using var tempStream = new MemoryStream(); publicKey.Write(new BinaryStreamWriter(tempStream)); - var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var reader = new BinaryStreamReader(tempStream.ToArray()); var newPublicKey = StrongNamePublicKey.FromReader(ref reader); Assert.Equal(publicKey.Modulus, newPublicKey.Modulus); diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs index 06d769b89..a9699d22b 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs +++ b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs @@ -45,7 +45,7 @@ public void PersistentFixedVersionInfo() versionInfo.Write(new BinaryStreamWriter(tempStream)); // Reload. - var infoReader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var infoReader = new BinaryStreamReader(tempStream.ToArray()); var newVersionInfo = VersionInfoResource.FromReader(ref infoReader); var newFixedVersionInfo = newVersionInfo.FixedVersionInfo; @@ -117,7 +117,7 @@ public void PersistentVarFileInfo() versionInfo.Write(new BinaryStreamWriter(tempStream)); // Reload. - var infoReader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var infoReader = new BinaryStreamReader(tempStream.ToArray()); var newVersionInfo = VersionInfoResource.FromReader(ref infoReader); // Verify. @@ -152,7 +152,7 @@ public void PersistentStringFileInfo() versionInfo.Write(new BinaryStreamWriter(tempStream)); // Reload. - var infoReader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var infoReader = new BinaryStreamReader(tempStream.ToArray()); var newVersionInfo = VersionInfoResource.FromReader(ref infoReader); // Verify. diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index 889913627..32a23f448 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -20,7 +20,7 @@ private DbiStream GetDbiStream(bool rebuild) { using var stream = new MemoryStream(); dbiStream.Write(new BinaryStreamWriter(stream)); - dbiStream = DbiStream.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + dbiStream = DbiStream.FromReader(new BinaryStreamReader(stream.ToArray())); } return dbiStream; diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs index 03a19f01b..31f17c82e 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs @@ -19,7 +19,7 @@ private static InfoStream GetInfoStream(bool rebuild) { using var stream = new MemoryStream(); infoStream.Write(new BinaryStreamWriter(stream)); - infoStream = InfoStream.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + infoStream = InfoStream.FromReader(new BinaryStreamReader(stream.ToArray())); } return infoStream; diff --git a/test/AsmResolver.Tests/IO/BinaryStreamReaderTest.cs b/test/AsmResolver.Tests/IO/BinaryStreamReaderTest.cs index 0a3757d56..2c4c201c4 100644 --- a/test/AsmResolver.Tests/IO/BinaryStreamReaderTest.cs +++ b/test/AsmResolver.Tests/IO/BinaryStreamReaderTest.cs @@ -11,7 +11,7 @@ public class BinaryStreamReaderTest [Fact] public void EmptyArray() { - var reader = ByteArrayDataSource.CreateReader(new byte[0]); + var reader = new BinaryStreamReader(new byte[0]); Assert.Equal(0u, reader.Length); Assert.Throws(() => reader.ReadByte()); @@ -21,7 +21,7 @@ public void EmptyArray() [Fact] public void ReadByte() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x80, 0x80 @@ -37,7 +37,7 @@ public void ReadByte() [Fact] public void ReadInt16() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x80, 0x02, 0x80 @@ -52,7 +52,7 @@ public void ReadInt16() [Fact] public void ReadInt32() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x04, 0x03, 0x02, 0x81, 0x08, 0x07, 0x06, 0x85 @@ -68,7 +68,7 @@ public void ReadInt32() [Fact] public void ReadInt64() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x80, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x88, @@ -90,14 +90,14 @@ public void ReadBinaryFormatterString(string value) var writer = new BinaryWriter(stream, Encoding.UTF8); writer.Write(value); - var reader = ByteArrayDataSource.CreateReader(stream.ToArray()); + var reader = new BinaryStreamReader(stream.ToArray()); Assert.Equal(value, reader.ReadBinaryFormatterString()); } [Fact] public void NewForkSubRange() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -112,7 +112,7 @@ public void NewForkSubRange() [Fact] public void NewForkInvalidStart() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -123,7 +123,7 @@ public void NewForkInvalidStart() [Fact] public void NewForkTooLong() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -134,7 +134,7 @@ public void NewForkTooLong() [Fact] public void ForkReadsSameData() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -146,7 +146,7 @@ public void ForkReadsSameData() [Fact] public void ForkMovesIndependentOfOriginal() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -161,7 +161,7 @@ public void ForkMovesIndependentOfOriginal() [Fact] public void ForkStartAtMiddle() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -173,7 +173,7 @@ public void ForkStartAtMiddle() [Fact] public void ForkOfFork() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); diff --git a/test/AsmResolver.Tests/IO/BinaryStreamWriterTest.cs b/test/AsmResolver.Tests/IO/BinaryStreamWriterTest.cs index adc93d2d1..2c646ed23 100644 --- a/test/AsmResolver.Tests/IO/BinaryStreamWriterTest.cs +++ b/test/AsmResolver.Tests/IO/BinaryStreamWriterTest.cs @@ -84,7 +84,7 @@ public void Write7BitEncodedInt32(int value) writer.Write7BitEncodedInt32(value); - var reader = ByteArrayDataSource.CreateReader(stream.ToArray()); + var reader = new BinaryStreamReader(stream.ToArray()); Assert.Equal(value, reader.Read7BitEncodedInt32()); }