diff --git a/AsmResolver.sln b/AsmResolver.sln index 90430c976..0cf5601e0 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -83,12 +83,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution LICENSE.md = LICENSE.md README.md = README.md Directory.Build.props = Directory.Build.props + appveyor.yml = appveyor.yml EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.Pdb", "src\AsmResolver.Symbols.Pdb\AsmResolver.Symbols.Pdb.csproj", "{9E311832-D0F2-42CA-84DD-9A91B88F0287}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.Pdb.Tests", "test\AsmResolver.Symbols.Pdb.Tests\AsmResolver.Symbols.Pdb.Tests.csproj", "{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.DotNet.Dynamic.Tests", "test\AsmResolver.DotNet.Dynamic.Tests\AsmResolver.DotNet.Dynamic.Tests.csproj", "{C089D0AB-B428-4136-89CC-7974CB590513}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsmResolver.DotNet.Dynamic", "src\AsmResolver.DotNet.Dynamic\AsmResolver.DotNet.Dynamic.csproj", "{62420213-67AD-40FC-B451-BD05C2437CE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -451,6 +456,30 @@ Global {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.Build.0 = Release|Any CPU {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.ActiveCfg = Release|Any CPU {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.Build.0 = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x64.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x64.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x86.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x86.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|Any CPU.Build.0 = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x64.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x64.Build.0 = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x86.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x86.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x64.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x64.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x86.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|Any CPU.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x64.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x64.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x86.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -490,6 +519,8 @@ Global {2D1DF5DA-7367-4490-B3F0-B996348E150B} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0} {9E311832-D0F2-42CA-84DD-9A91B88F0287} = {34A95168-A162-4F6A-803B-B6F221FE9EA6} {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE} = {786C1732-8C96-45DD-97BB-639C9AA7F45B} + {C089D0AB-B428-4136-89CC-7974CB590513} = {786C1732-8C96-45DD-97BB-639C9AA7F45B} + {62420213-67AD-40FC-B451-BD05C2437CE3} = {34A95168-A162-4F6A-803B-B6F221FE9EA6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3302AC79-6D23-4E7D-8C5F-C0C7261044D0} diff --git a/README.md b/README.md index 31582ebfe..9bf2ec12f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -AsmResolver -=========== +# AsmResolver [![Master branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/master.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/master) [![Nuget feed](https://img.shields.io/nuget/v/AsmResolver.svg)](https://www.nuget.org/packages/AsmResolver/) @@ -12,8 +11,44 @@ AsmResolver is a PE inspection library allowing .NET programmers to read, modify AsmResolver is released under the MIT license. -Binaries --------- +## Features + +AsmResolver has a lot of features. Below a non-exhaustive list: + +- [x] Create, read and write PE files + - [x] Inspect and update PE headers. + - [x] Create, read and write sections. +- [x] Create, read and write various data directories + - [x] Debug Directory (CodeView) + - [x] .NET Directory + - [x] CIL assembler and disassemblers + - [x] Metadata Directory (tables, strings, user-strings, blobs, GUIDs) + - [x] Resources Directory + - [x] Strong Name Signing + - [x] VTable Fixup Directory + - [x] Exception Directory (AMD64) + - [x] Export Directory + - [x] Import Directory + - [x] Base Relocation Directory + - [x] TLS Directory + - [x] Win32 Resources Directory +- [x] Fully mutable object model for .NET modules that is similar to System.Reflection + - [x] Strong type-system with many useful factory methods for quickly constructing new metadata. + - [x] Full metadata importing and cloning (useful for injecting metadata into another assembly) + - [x] .NET Framework 2.0+, .NET Core and .NET 5+ binary file support. + - [x] Infer memory layout of types statically. + - [x] Create, read and write managed resource sets (`.resources` files) + - [x] Create new method bodies containing native code. + - [x] Highly configurable reader and writer options and custom error handling for both. + - [x] Rich support for AppHost and SingleFileHost bundled files. + + +## Documentation + +Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library. + + +## Binaries Stable builds: @@ -30,13 +65,7 @@ Nightly builds: | development | [![Development branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/development.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/development) -Documentation -------------- -Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library. - - -Compiling ---------- +## Compiling The solution can be build using the .NET SDK or an IDE that works with the .NET SDK (such as Visual Studio and JetBrains Rider). The main packages target .NET Standard 2.0, and the xUnit test projects target .NET Core 3.1. @@ -51,20 +80,18 @@ To run all tests, simply run: $ dotnet test ``` +## Contributing -Contributing ------------- See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on general workflow and code style. -Found a bug or have questions? ------------------------------- +## Found a bug or have questions? + Please use the [issue tracker](https://github.com/Washi1337/AsmResolver/issues). Try to be as descriptive as possible. You can also join the [Discord](https://discord.gg/Y7DTBkbhJJ) to engage more directly with the community. -Acknowledgements ----------------- +## Acknowledgements AsmResolver started out as a hobby project, but has grown into a community project with various contributors. Without these people, AsmResolver would not have been where it is today! @@ -73,4 +100,3 @@ AsmResolver started out as a hobby project, but has grown into a community proje - Another big thank you to all the people that suggested new features, provided feedback on the API design, have done extensive testing, and/or reported bugs on the [issue board](https://github.com/Washi1337/AsmResolver/issues), by e-mail, or through DMs. If you feel you have been under-represented in these acknowledgements, feel free to contact me. - diff --git a/appveyor.yml b/appveyor.yml index a350fd134..6c4e999bc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 4.11.0-master-build.{build} + version: 4.11.2-master-build.{build} configuration: Release skip_commits: @@ -23,7 +23,7 @@ deploy: provider: NuGet api_key: - secure: DCeHUu0aAsOjRnoi2DpcuXpj0apD7dxHzglSamP7LGzcZjhIvTBi1ONnjIa7L2zm + secure: L3fXsS7umzD8zwAvTsdGxOg/E6tQ4IR4MfwBAcO8elE7ZwjZ8HO8UPwjiWbp4RMw skip_symbols: false artifact: /.*\.nupkg/ diff --git a/docs/peimage/tls.rst b/docs/peimage/tls.rst index 564b25600..ad2f97ac0 100644 --- a/docs/peimage/tls.rst +++ b/docs/peimage/tls.rst @@ -6,7 +6,7 @@ Executables that use multiple threads might require static (non-stack) memory th All code relevant to the TLS data directory of a PE resides in the following namespace: .. code-block:: csharp - + using AsmResolver.PE.Tls; @@ -21,7 +21,7 @@ The PE file format defines a segment of memory within the TLS data directory tha .. code-block:: csharp - + var indexSegment = new DataSegment(new byte[8]); var directory = new TlsDirectory @@ -48,18 +48,14 @@ Next to static initialization data, it is also possible to specify a list of fun Creating new TLS directories ---------------------------- -Since the TLS data directory stores its data using virtual addresses (VA) rather than relative virtual addresses (RVA), AsmResolver requires the image base as well as the pointer size. This is done through the ``ImageBase`` and ``Is32Bit`` properties. By default, the following values are assumed: +Adding a new TLS directory to an image can be done using the parameterless constructor of the ``TlsDirectory`` class: .. code-block:: csharp - var directory = new TlsDirectory(); - directory.ImageBase = 0x00400000; - directory.Is32Bit = true; - - -Typically, you should make sure they are in sync with the values found in the file and optional header of the final PE file. Upon reading from an existing PE file, these two properties are initialized to the values stored in these two headers. + var tlsDirectory = new TlsDirectory(); + image.TlsDirectory = tlsDirectory; -When building a relocatable PE file, you might also need to add base address relocations to the VAs inside the TLS directory. To quickly get all the base relocations required, use the ``GetRequiredBaseRelocations`` method: +A TLS directory references all its sub segments using virtual addresses (VA) rather than relative addresses (RVA). This means that constructing a relocatable PE image with a TLS directory requires base relocation entries to be registered that let the Windows PE loader rebase all virtual addresses used in the directory when necessary. To quickly register all the required base relocations, you can call the ``GetRequiredBaseRelocations`` method and add all returned entries to the base relocation directory of the PE image: .. code-block:: csharp @@ -67,5 +63,5 @@ When building a relocatable PE file, you might also need to add base address rel IPEImage image = ...; - foreach (var relocation in image.TlsDirectory.GetRequiredBaseRelocations()) - image.Relocations.Add(relocation); \ No newline at end of file + foreach (var relocation in tlsDirectory.GetRequiredBaseRelocations()) + image.Relocations.Add(relocation); diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj new file mode 100644 index 000000000..1f4ff4172 --- /dev/null +++ b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj @@ -0,0 +1,32 @@ + + + + AsmResolver.DotNet.Dynamic + Dynamic method support for the AsmResolver executable file inspection toolsuite. + exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly dynamic + true + 1701;1702;NU5105 + net6.0;netcoreapp3.1;netstandard2.0 + enable + + + + bin\Debug\AsmResolver.DotNet.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..4349d9006 --- /dev/null +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Serialized; +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; +using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; + +namespace AsmResolver.DotNet.Dynamic +{ + /// + /// Represents a single method in a type definition of a .NET module. + /// + public class DynamicMethodDefinition : MethodDefinition + { + /// + /// Create a Dynamic Method Definition + /// + /// Target Module + /// Dynamic Method / Delegate / DynamicResolver + public DynamicMethodDefinition(ModuleDefinition module, object dynamicMethodObj) : + base(new MetadataToken(TableIndex.Method, 0)) + { + dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); + var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method"); + if (methodBase is null) + { + throw new ArgumentException( + "Could not get the underlying method base in the provided dynamic method object."); + } + + Module = module; + Name = methodBase.Name; + Attributes = (MethodAttributes)methodBase.Attributes; + Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase, module)); + CilMethodBody = CreateDynamicMethodBody(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. + private static CilMethodBody CreateDynamicMethodBody(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 63% rename from src/AsmResolver.DotNet/DynamicMethodHelper.cs rename to src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index f3cece5db..118fd49ac 100644 --- a/src/AsmResolver.DotNet/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 +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,14 +45,58 @@ public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition methodBody.LocalVariables.Add(new CilLocalVariable(localsSignature.VariableTypes[i])); } + private static TypeSignature ReadTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) + { + return (ElementType) reader.PeekByte() == ElementType.Internal + ? ReadInternalTypeSignature(context, ref reader) + : TypeSignature.FromReader(in context, ref reader); + } + + private static TypeSignature ReadInternalTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) + { + var address = IntPtr.Size switch + { + 4 => new IntPtr(reader.ReadInt32()), + _ => new IntPtr(reader.ReadInt64()) + }; + + // Let the runtime translate the address to a type and import it. + var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address }); + + var type = clrType is not null + ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType) + : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); + + return new TypeDefOrRefSignature(type); + } + + private static LocalVariablesSignature ReadLocalVariableSignature( + in BlobReadContext context, + ref BinaryStreamReader reader) + { + var result = new LocalVariablesSignature(); + result.Attributes = (CallingConventionAttributes) reader.ReadByte(); + + if (!reader.TryReadCompressedUInt32(out uint count)) + { + context.ReaderContext.BadImage("Invalid number of local variables in local variable signature."); + return result; + } + + for (int i = 0; i < count; i++) + result.VariableTypes.Add(ReadTypeSignature(context, ref reader)); + + return result; + } + public static void ReadReflectionExceptionHandlers(CilMethodBody methodBody, IList? ehInfos, byte[] ehHeader, ReferenceImporter importer) { //Sample needed! - if (ehHeader is {Length: > 4}) + if (ehHeader is { Length: > 4 }) throw new NotImplementedException("Exception handlers from ehHeader not supported yet."); - if (ehInfos is {Count: > 0}) + if (ehInfos is { Count: > 0 }) { foreach (var ehInfo in ehInfos) InterpretEHInfo(methodBody, importer, ehInfo); @@ -61,7 +120,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 +140,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 +169,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/FieldReader.cs b/src/AsmResolver.DotNet.Dynamic/FieldReader.cs similarity index 96% rename from src/AsmResolver.DotNet/FieldReader.cs rename to src/AsmResolver.DotNet.Dynamic/FieldReader.cs index 8133f1e63..40e4a4450 100644 --- a/src/AsmResolver.DotNet/FieldReader.cs +++ b/src/AsmResolver.DotNet.Dynamic/FieldReader.cs @@ -1,6 +1,6 @@ -using System.Reflection; +using System.Reflection; -namespace AsmResolver.DotNet +namespace AsmResolver.DotNet.Dynamic { internal static class FieldReader { diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 456ce3119..8b9a38a50 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -8,6 +8,7 @@ 1701;1702;NU5105 enable net6.0;netcoreapp3.1;netstandard2.0 + true @@ -23,11 +24,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/AsmResolver.DotNet/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/DotNetDirectoryBuffer.CodedIndices.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs index 37b5c136b..2aeee551a 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs @@ -26,16 +26,16 @@ private void AddCustomAttribute(MetadataToken ownerToken, CustomAttribute attrib table.Add(attribute, row); } - private uint AddResolutionScope(IResolutionScope? scope) + private uint AddResolutionScope(IResolutionScope? scope, bool allowDuplicates, bool preserveRid) { if (!AssertIsImported(scope)) return 0; var token = scope.MetadataToken.Table switch { - TableIndex.AssemblyRef => GetAssemblyReferenceToken(scope as AssemblyReference), - TableIndex.TypeRef => GetTypeReferenceToken(scope as TypeReference), - TableIndex.ModuleRef => GetModuleReferenceToken(scope as ModuleReference), + TableIndex.AssemblyRef => AddAssemblyReference(scope as AssemblyReference, allowDuplicates, preserveRid), + TableIndex.TypeRef => AddTypeReference(scope as TypeReference, allowDuplicates, preserveRid), + TableIndex.ModuleRef => AddModuleReference(scope as ModuleReference, allowDuplicates, preserveRid), TableIndex.Module => 0, _ => throw new ArgumentOutOfRangeException(nameof(scope)) }; diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs index 1abba1f59..b3eca662b 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs @@ -10,7 +10,10 @@ public partial class DotNetDirectoryBuffer : IMetadataTokenProvider public uint GetUserStringIndex(string value) => Metadata.UserStringsStream.GetStringIndex(value); /// - public MetadataToken GetTypeReferenceToken(TypeReference? type) => AddTypeReference(type, false); + public MetadataToken GetTypeReferenceToken(TypeReference? type) + { + return AddTypeReference(type, false, false); + } /// /// Adds a type reference to the buffer. @@ -20,19 +23,25 @@ public partial class DotNetDirectoryBuffer : IMetadataTokenProvider /// true if the row is always to be added to the end of the buffer, false if a duplicated row /// is supposed to be removed and the token of the original should be returned instead. /// + /// + /// true if the metadata token of the type should be preserved, false otherwise. + /// /// The newly assigned metadata token. - public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates) + public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates, bool preserveRid) { if (!AssertIsImported(type)) return MetadataToken.Zero; var table = Metadata.TablesStream.GetDistinctTable(TableIndex.TypeRef); var row = new TypeReferenceRow( - AddResolutionScope(type.Scope), + AddResolutionScope(type.Scope, allowDuplicates, preserveRid), Metadata.StringsStream.GetStringIndex(type.Name), Metadata.StringsStream.GetStringIndex(type.Namespace)); - var token = table.Add(row, allowDuplicates); + var token = preserveRid + ? table.Insert(type.MetadataToken.Rid, row, allowDuplicates) + : table.Add(row, allowDuplicates); + _tokenMapping.Register(type, token); AddCustomAttributes(token, type); return token; @@ -164,7 +173,7 @@ public MetadataToken AddStandAloneSignature(StandAloneSignature? signature, bool /// public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly) { - return AddAssemblyReference(assembly, false); + return AddAssemblyReference(assembly, false, false); } /// @@ -175,8 +184,11 @@ public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly) /// true if the row is always to be added to the end of the buffer, false if a duplicated row /// is supposed to be removed and the token of the original should be returned instead. /// + /// + /// true if the metadata token of the assembly should be preserved, false otherwise. + /// /// The newly assigned metadata token. - public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates) + public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates, bool preserveRid) { if (assembly is null || !AssertIsImported(assembly)) return MetadataToken.Zero; @@ -193,7 +205,10 @@ public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allo Metadata.StringsStream.GetStringIndex(assembly.Culture), Metadata.BlobStream.GetBlobIndex(assembly.HashValue)); - var token = table.Add(row, allowDuplicates); + var token = preserveRid + ? table.Insert(assembly.MetadataToken.Rid, row, allowDuplicates) + : table.Add(row, allowDuplicates); + AddCustomAttributes(token, assembly); return token; } @@ -205,7 +220,7 @@ public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allo /// The new metadata token assigned to the module reference. public MetadataToken GetModuleReferenceToken(ModuleReference? reference) { - return AddModuleReference(reference, false); + return AddModuleReference(reference, false, false); } /// @@ -216,8 +231,11 @@ public MetadataToken GetModuleReferenceToken(ModuleReference? reference) /// true if the row is always to be added to the end of the buffer, false if a duplicated row /// is supposed to be removed and the token of the original should be returned instead. /// + /// + /// true if the metadata token of the module should be preserved, false otherwise. + /// /// The newly assigned metadata token. - public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates) + public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates, bool preserveRid) { if (!AssertIsImported(reference)) return MetadataToken.Zero; @@ -225,7 +243,10 @@ public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDu var table = Metadata.TablesStream.GetDistinctTable(TableIndex.ModuleRef); var row = new ModuleReferenceRow(Metadata.StringsStream.GetStringIndex(reference.Name)); - var token = table.Add(row, allowDuplicates); + var token = preserveRid + ? table.Insert(reference.MetadataToken.Rid, row, allowDuplicates) + : table.Add(row, allowDuplicates); + AddCustomAttributes(token, reference); return token; } diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs index 705fda930..7750a6146 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs @@ -212,24 +212,27 @@ private void ImportBasicTablesIfSpecified(ModuleDefinition module, DotNetDirecto if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveAssemblyReferenceIndices) != 0) { ImportTables(module, TableIndex.AssemblyRef, - r => buffer.AddAssemblyReference(r, true)); + r => buffer.AddAssemblyReference(r, true, true)); } if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveModuleReferenceIndices) != 0) { ImportTables(module, TableIndex.ModuleRef, - r => buffer.AddModuleReference(r, true)); + r => buffer.AddModuleReference(r, true, true)); } if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeReferenceIndices) != 0) { ImportTables(module, TableIndex.TypeRef, - r => buffer.AddTypeReference(r, true)); + r => buffer.AddTypeReference(r, true, true)); } } private void ImportTypeSpecsAndMemberRefsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) { + if (module.DotNetDirectory is null) + return; + if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeSpecificationIndices) != 0) { ImportTables(module, TableIndex.TypeSpec, diff --git a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs index 66ff6f0a8..c67b17956 100644 --- a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs +++ b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs @@ -70,7 +70,7 @@ public PEImageBuildResult CreateImage(ModuleDefinition module) }; // Construct new .NET directory. - var symbolProvider = new NativeSymbolsProvider(image.ImageBase); + var symbolProvider = new NativeSymbolsProvider(); var result = DotNetDirectoryFactory.CreateDotNetDirectory( module, symbolProvider, diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs index 2a7ecdef5..ac966743d 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs @@ -60,6 +60,33 @@ public void EnsureCapacity(int capacity) /// public MetadataToken Add(in TRow row) => Add(row, false); + /// + public MetadataToken Insert(uint rid, in TRow row) => Insert(rid, row, false); + + /// + /// Inserts a row into the metadata table at the provided row identifier. + /// + /// The row identifier. + /// The row to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// The metadata token that this row was assigned to. + public MetadataToken Insert(uint rid, in TRow row, bool allowDuplicates) + { + if (!_entries.TryGetValue(row, out var token)) + { + token = _underlyingBuffer.Insert(rid, in row); + _entries.Add(row, token); + } + else if (allowDuplicates) + { + token = _underlyingBuffer.Insert(rid, in row); + } + + return token; + } + /// /// Adds a row to the metadata table buffer. /// diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs index 66a6c1836..b73b5061a 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs @@ -62,5 +62,13 @@ TRow this[uint rid] /// The row to add. /// The metadata token that this row was assigned to. MetadataToken Add(in TRow row); + + /// + /// Inserts a row into the metadata table at the provided row identifier. + /// + /// The row identifier. + /// The row to add. + /// The metadata token that this row was assigned to. + MetadataToken Insert(uint rid, in TRow row); } } diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs index 8e9d4395f..17717b185 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs @@ -15,7 +15,9 @@ public class UnsortedMetadataTableBuffer : IMetadataTableBuffer where TRow : struct, IMetadataRow { private readonly RefList _entries = new(); + private readonly BitList _available = new(); private readonly MetadataTable _table; + private uint _currentRid; /// /// Creates a new unsorted metadata table buffer. @@ -33,7 +35,11 @@ public UnsortedMetadataTableBuffer(MetadataTable table) public virtual TRow this[uint rid] { get => _entries[(int) (rid - 1)]; - set => _entries[(int) (rid - 1)] = value; + set + { + _entries[(int) (rid - 1)] = value; + _available[(int) (rid - 1)] = false; + } } /// @@ -49,8 +55,45 @@ public void EnsureCapacity(int capacity) /// public virtual MetadataToken Add(in TRow row) { - _entries.Add(row); - return new MetadataToken(_table.TableIndex, (uint) _entries.Count); + // Move over unavailable slots. + while (_currentRid < _available.Count && !_available[(int) _currentRid]) + _currentRid++; + + // If we moved over all entries, we're adding to the end. + if (_currentRid == _entries.Count) + { + _currentRid++; + } + + return Insert(_currentRid++, row); + } + + /// + public MetadataToken Insert(uint rid, in TRow row) + { + EnsureRowsAllocated(rid); + + var token = new MetadataToken(_table.TableIndex, rid); + + if (!_available[(int) (rid - 1)]) + { + if (EqualityComparer.Default.Equals(row, _entries[(int) (rid - 1)])) + return token; + + throw new InvalidOperationException($"Token 0x{token.ToString()} is already in use."); + } + + this[rid] = row; + return token; + } + + private void EnsureRowsAllocated(uint rid) + { + while (_entries.Count < rid) + { + _entries.Add(default); + _available.Add(true); + } } /// diff --git a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs index faec68be5..95aef83eb 100644 --- a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs +++ b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs @@ -34,7 +34,7 @@ public IPEImage? ConstructedImage /// Gets a value indicating whether the image was constructed successfully or not. /// [MemberNotNullWhen(false, nameof(ConstructedImage))] - public bool HasFailed => ConstructedImage is null; + public bool HasFailed => DiagnosticBag.IsFatal; /// /// Gets the bag containing the diagnostics that were collected during the construction of the image. diff --git a/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs b/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs index 44ceec89c..e0f512a63 100644 --- a/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs @@ -61,7 +61,7 @@ public void MapTokenToExport(UnmanagedExportInfo exportInfo, MetadataToken token vtableFixup.Tokens.Add(token); var vtableSymbol = new Symbol(vtableFixup.Tokens.GetReferenceToIndex(vtableFixup.Tokens.Count - 1)); - var thunkStub = _targetPlatform.CreateThunkStub(0x00400000, vtableSymbol); + var thunkStub = _targetPlatform.CreateThunkStub(vtableSymbol); // Register exported symbol. var stubReference = thunkStub.Segment.ToReference(); diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index 2acc2a57b..c3a6025cf 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -445,7 +445,7 @@ private void WriteFileContents(IBinaryStreamWriter writer, uint alignment) if (file.Type == BundleFileType.Assembly) writer.Align(alignment); - file.Contents.UpdateOffsets(writer.Offset, (uint) writer.Offset); + file.Contents.UpdateOffsets(new RelocationParameters(writer.Offset, (uint) writer.Offset)); file.Contents.Write(writer); } } diff --git a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs new file mode 100644 index 000000000..efa0063fd --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs @@ -0,0 +1,24 @@ +using System; + +namespace AsmResolver.DotNet.Cloning +{ + /// + /// This implementation that calls the to a callback action. + /// + public class CallbackCloneListener : MemberClonerListener + { + + private readonly Action _callback; + + /// + /// Creates a new instance of the class. + /// + /// The Callback used. + public CallbackCloneListener(Action callback) => + _callback = callback; + + /// + 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 new file mode 100644 index 000000000..1a6ecaada --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs @@ -0,0 +1,45 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Callback listener that receives calls after cloning process. + /// + public interface IMemberClonerListener + { + /// + /// This function is called for every member that got cloned. + /// + /// The original member. + /// The cloned member. + public void OnClonedMember(IMetadataMember original, IMetadataMember cloned); + /// + /// This function is called for every type that got cloned. + /// + /// The original type. + /// The cloned type. + public void OnClonedType(TypeDefinition original, TypeDefinition cloned); + /// + /// This function is called for every method that got cloned. + /// + /// The original method. + /// The cloned method. + public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned); + /// + /// This function is called for every field that got cloned. + /// + /// The original field. + /// The cloned field. + public void OnClonedField(FieldDefinition original, FieldDefinition cloned); + /// + /// This function is called for every property that got cloned. + /// + /// The original property. + /// The cloned property. + public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned); + /// + /// This function is called for every event that got cloned. + /// + /// The original event. + /// The cloned event. + public void OnClonedEvent(EventDefinition original, EventDefinition cloned); + } +} diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs index b68ab9c81..78df400d3 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs @@ -39,7 +39,12 @@ private static FieldDefinition CreateFieldStub(MemberCloneContext context, Field private void DeepCopyFields(MemberCloneContext context) { foreach (var field in _fieldsToClone) + { DeepCopyField(context, field); + var clonedMember = (FieldDefinition)context.ClonedMembers[field]; + _clonerListener.OnClonedMember(field, clonedMember); + _clonerListener.OnClonedField(field, clonedMember); + } } private void DeepCopyField(MemberCloneContext context, FieldDefinition field) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs index a7c2dc227..af6085a14 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs @@ -54,7 +54,12 @@ private static ParameterDefinition CloneParameterDefinition(MemberCloneContext c private void DeepCopyMethods(MemberCloneContext context) { foreach (var method in _methodsToClone) + { DeepCopyMethod(context, method); + var clonedMember = (MethodDefinition)context.ClonedMembers[method]; + _clonerListener.OnClonedMember(method, clonedMember); + _clonerListener.OnClonedMethod(method, clonedMember); + } } private void DeepCopyMethod(MemberCloneContext context, MethodDefinition method) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs index 10f3f30c4..7d3e5ea6b 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs @@ -17,6 +17,9 @@ private void DeepCopyProperties(MemberCloneContext context) { declaringType.Properties.Add(clonedProperty); } + var clonedMember = clonedProperty; + _clonerListener.OnClonedMember(property, clonedMember); + _clonerListener.OnClonedProperty(property, clonedMember); } } @@ -51,6 +54,9 @@ private void DeepCopyEvents(MemberCloneContext context) { declaringType.Events.Add(clonedEvent); } + var clonedMember = clonedEvent; + _clonerListener.OnClonedMember(@event, clonedMember); + _clonerListener.OnClonedEvent(@event, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 1580d1efa..af5d27abd 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,18 +31,35 @@ public partial class MemberCloner /// Creates a new instance of the class. /// /// The target module to copy the members into. - public MemberCloner(ModuleDefinition targetModule) : this(targetModule, null) { } + public MemberCloner(ModuleDefinition targetModule) : this(targetModule, (original, cloned) => { }) { } + + /// + /// Creates a new instance of the class. + /// + /// 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)) { } + + /// + /// Creates a new instance of the class. + /// + /// The target module to copy the members into. + /// The callback listener used in the cloner. + public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listener) : this(targetModule, null, listener) { } /// /// Creates a new instance of the class. /// /// The target module to copy the members into. /// The factory for creating the reference importer + /// The listener used in the cloner. public MemberCloner(ModuleDefinition targetModule, - Func? importerFactory) + Func? importerFactory, + IMemberClonerListener clonerListener) { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); _importerFactory = importerFactory; + _clonerListener = clonerListener; } /// @@ -272,7 +290,12 @@ private void DeepCopyMembers(MemberCloneContext context) private void DeepCopyTypes(MemberCloneContext context) { foreach (var type in _typesToClone) + { DeepCopyType(context, type); + var clonedMember = (TypeDefinition)context.ClonedMembers[type]; + _clonerListener.OnClonedMember(type, clonedMember); + _clonerListener.OnClonedType(type, clonedMember); + } } private void DeepCopyType(MemberCloneContext context, TypeDefinition type) diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs new file mode 100644 index 000000000..49bea854a --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs @@ -0,0 +1,19 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + public abstract class MemberClonerListener : IMemberClonerListener + { + /// + public virtual void OnClonedMember(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/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/Code/Native/INativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs index 30d0f2181..fa7dfac36 100644 --- a/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs +++ b/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs @@ -8,14 +8,6 @@ namespace AsmResolver.DotNet.Code.Native /// public interface INativeSymbolsProvider { - /// - /// Gets or sets the image base the final PE image is using. - /// - ulong ImageBase - { - get; - } - /// /// Adds a single symbol to the prototype. /// diff --git a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs index e4253b717..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.DotNet/Code/Native/NativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs index 9a1e97273..b09c1e09f 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs @@ -22,21 +22,6 @@ public class NativeSymbolsProvider : INativeSymbolsProvider private uint _maxExportedOrdinal = 0; private readonly List _floatingExportedSymbols = new(); - /// - /// Creates a new instance of the class. - /// - /// The image base of the final PE image. - public NativeSymbolsProvider(ulong imageBase) - { - ImageBase = imageBase; - } - - /// - public ulong ImageBase - { - get; - } - /// public ISymbol ImportSymbol(ISymbol symbol) { diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs index 30bab5716..18e57b9e5 100644 --- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs +++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs @@ -15,7 +15,7 @@ public class DefaultMetadataResolver : IMetadataResolver private readonly ConcurrentDictionary _typeCache; private readonly SignatureComparer _comparer = new() { - AcceptNewerAssemblyVersionNumbers = true + IgnoreAssemblyVersionNumbers = true }; /// diff --git a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs index 33813a6ed..d289f45f5 100644 --- a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs +++ b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs @@ -53,6 +53,21 @@ public Version Version get; } + /// + /// Gets a value indicating whether the application targets the .NET or .NET Core runtime or not. + /// + public bool IsNetCoreApp => Name == NetCoreApp; + + /// + /// Gets a value indicating whether the application targets the .NET Framework runtime or not. + /// + public bool IsNetFramework => Name == NetFramework; + + /// + /// Gets a value indicating whether the application targets the .NET standard specification or not. + /// + public bool IsNetStandard => Name == NetStandard; + /// /// Attempts to parse the framework name as provided in . /// diff --git a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet/DynamicMethodDefinition.cs deleted file mode 100644 index b95d8f003..000000000 --- a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Reflection; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Signatures; -using AsmResolver.DotNet.Signatures.Types; -using AsmResolver.PE.DotNet.Metadata.Tables; -using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; - -namespace AsmResolver.DotNet -{ - /// - /// Represents a single method in a type definition of a .NET module. - /// - public class DynamicMethodDefinition : MethodDefinition - { - /// - /// Create a Dynamic Method Definition - /// - /// Target Module - /// Dynamic Method / Delegate / DynamicResolver - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver and FromDynamicMethod")] - public DynamicMethodDefinition(ModuleDefinition module,object dynamicMethodObj) : - base(new MetadataToken(TableIndex.Method, 0)) - { - dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); - var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method"); - if (methodBase is null) - { - throw new ArgumentException( - "Could not get the underlying method base in the provided dynamic method object."); - } - - Module = module; - Name = methodBase.Name; - Attributes = (MethodAttributes)methodBase.Attributes; - Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase,module)); - CilMethodBody = CilMethodBody.FromDynamicMethod(this,dynamicMethodObj); - } - - private MethodSignature ResolveSig(MethodBase methodBase,ModuleDefinition module) - { - var imp = new ReferenceImporter(module); - var returnType = methodBase is MethodInfo info - ? imp.ImportTypeSignature(info.ReturnType) - : module.CorLibTypeFactory.Void; - - var parameters = methodBase.GetParameters(); - - var parameterTypes = new TypeSignature[parameters.Length]; - for (int i = 0; i < parameterTypes.Length; i++) - parameterTypes[i] = imp.ImportTypeSignature(parameters[i].ParameterType); - - return new MethodSignature( - methodBase.IsStatic ? 0 : CallingConventionAttributes.HasThis, - returnType, parameterTypes); - } - - /// - public override ModuleDefinition Module { get; } - - } -} diff --git a/src/AsmResolver.DotNet/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/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 33d904554..65f1061cd 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -492,6 +492,7 @@ public virtual MethodSpecification ImportMethod(MethodSpecification method) /// /// The method to import. /// The imported method. + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls System.Reflection.Module.ResolveMethod(int)")] public virtual IMethodDescriptor ImportMethod(MethodBase method) { if (method is null) @@ -522,6 +523,7 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method) return new MemberReference(ImportType(method.DeclaringType), method.Name, result); } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls AsmResolver.DotNet.ReferenceImporter.ImportMethod(System.Reflection.MethodBase)")] private IMethodDescriptor ImportGenericMethod(MethodInfo method) { var memberRef = (IMethodDefOrRef) ImportMethod(method.GetGenericMethodDefinition()); @@ -576,6 +578,7 @@ public FieldSignature ImportFieldSignature(FieldSignature signature) /// The field to import. /// The imported field. /// Occurs when a field is not added to a type. + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls System.Reflection.Module.ResolveField(int)")] public MemberReference ImportField(FieldInfo field) { if (field is null) diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index e05310b12..53a414283 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) { @@ -55,9 +57,9 @@ public GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType, private GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType, IEnumerable typeArguments) { - GenericType = genericType; + _genericType = genericType; _typeArguments = new List(typeArguments); - IsValueType = isValueType; + _isValueType = isValueType; } /// @@ -68,8 +70,12 @@ private GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType /// public ITypeDefOrRef GenericType { - get; - set; + get => _genericType; + set + { + _genericType = value; + _isValueType = _genericType.IsValueType; + } } /// @@ -97,10 +103,7 @@ public override string? Name public override ModuleDefinition? Module => GenericType.Module; /// - public override bool IsValueType - { - get; - } + public override bool IsValueType => _isValueType; /// public override TypeDefinition? Resolve() => GenericType.Resolve(); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index cab577dc4..f617beb31 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -7,6 +7,9 @@ namespace AsmResolver.DotNet.Signatures.Types /// public class TypeDefOrRefSignature : TypeSignature { + private ITypeDefOrRef _type; + private bool _isValueType; + /// /// Creates a new type signature referencing a type in a type metadata table. /// @@ -23,9 +26,8 @@ public TypeDefOrRefSignature(ITypeDefOrRef type) /// Indicates whether the referenced type is a value type or not. public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType) { - Type = type; - IsValueType = isValueType; - + _type = type; + _isValueType = isValueType; } /// @@ -33,7 +35,12 @@ public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType) /// public ITypeDefOrRef Type { - get; + get => _type; + set + { + _type = value; + _isValueType = value.IsValueType; + } } /// @@ -52,10 +59,7 @@ public ITypeDefOrRef Type public override ModuleDefinition? Module => Type.Module; /// - public override bool IsValueType - { - get; - } + public override bool IsValueType => _isValueType; /// public override TypeDefinition? Resolve() => Type.Resolve(); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 389411e30..7eb81bfbd 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -14,18 +14,6 @@ public abstract class TypeSignature : ExtendableBlobSignature, ITypeDescriptor { internal const string NullTypeToString = "<>"; - private static readonly MethodInfo GetTypeFromHandleUnsafeMethod; - - static TypeSignature() - { - GetTypeFromHandleUnsafeMethod = typeof(Type) - .GetMethod("GetTypeFromHandleUnsafe", - (BindingFlags) (-1), - null, - new[] {typeof(IntPtr)}, - null)!; - } - /// public abstract string? Name { @@ -156,19 +144,9 @@ public static TypeSignature FromReader(in BlobReadContext context, ref BinaryStr return new BoxedTypeSignature(FromReader(context, ref reader)); case ElementType.Internal: - var address = IntPtr.Size switch - { - 4 => new IntPtr(reader.ReadInt32()), - _ => new IntPtr(reader.ReadInt64()) - }; - - // Let the runtime translate the address to a type and import it. - var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address }); - var asmResType = clrType is not null - ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType) - : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); - - return new TypeDefOrRefSignature(asmResType); + throw new NotSupportedException( + "Encountered an COR_ELEMENT_TYPE_INTERNAL type signature which is not supported by this " + + " type signature reader. Use the AsmResolver.DotNet.Dynamic extension package instead."); default: throw new ArgumentOutOfRangeException($"Invalid or unsupported element type {elementType}."); diff --git a/src/AsmResolver.DotNet/TokenAllocator.cs b/src/AsmResolver.DotNet/TokenAllocator.cs index f02d606df..9f1cff05a 100644 --- a/src/AsmResolver.DotNet/TokenAllocator.cs +++ b/src/AsmResolver.DotNet/TokenAllocator.cs @@ -38,6 +38,9 @@ private void InitializeTable(IDotNetDirectory netDirectory) var tableStream = netDirectory.Metadata!.GetStream(); for (TableIndex index = 0; index < TableIndex.Max; index++) { + if (!index.IsValidTableIndex()) + continue; + var table = tableStream.GetTable(index); _buckets[(int) index] = new TokenBucket(new MetadataToken(index, (uint) table.Count + 1)); } diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index ce14eb497..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 CustomModifierTypeSignature MakeModifierType( public static GenericInstanceTypeSignature MakeGenericInstanceType( this ITypeDescriptor type, params TypeSignature[] typeArguments) { - return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), type.IsValueType, typeArguments); + return type.MakeGenericInstanceType(type.IsValueType, typeArguments); + } + + /// + /// Constructs a new pointer type signature with the provided type descriptor as element type. + /// as element type. + /// + /// The element type. + /// true if the type is a value type, false otherwise. + /// The arguments to instantiate the type with. + /// The constructed by-reference type signature. + /// + /// This function can be used to avoid type resolution on type references. + /// + public static GenericInstanceTypeSignature MakeGenericInstanceType( + this ITypeDescriptor type, bool isValueType, params TypeSignature[] typeArguments) + { + return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), isValueType, typeArguments); } /// diff --git a/src/AsmResolver.DotNet/TypeReference.cs b/src/AsmResolver.DotNet/TypeReference.cs index 0b1332fe9..fef21335e 100644 --- a/src/AsmResolver.DotNet/TypeReference.cs +++ b/src/AsmResolver.DotNet/TypeReference.cs @@ -130,10 +130,13 @@ public IList CustomAttributes ITypeDefOrRef ITypeDescriptor.ToTypeDefOrRef() => this; /// - public TypeSignature ToTypeSignature() + public TypeSignature ToTypeSignature() => ToTypeSignature(IsValueType); + + /// + public TypeSignature ToTypeSignature(bool isValueType) { return Module?.CorLibTypeFactory.FromType(this) as TypeSignature - ?? new TypeDefOrRefSignature(this, IsValueType); + ?? new TypeDefOrRefSignature(this, isValueType); } /// diff --git a/src/AsmResolver.DotNet/TypeSpecification.cs b/src/AsmResolver.DotNet/TypeSpecification.cs index e8d805b4d..aa4420351 100644 --- a/src/AsmResolver.DotNet/TypeSpecification.cs +++ b/src/AsmResolver.DotNet/TypeSpecification.cs @@ -95,6 +95,9 @@ public IList CustomAttributes public TypeSignature ToTypeSignature() => Signature ?? throw new ArgumentException("Signature embedded into the type specification is null."); + /// + public TypeSignature ToTypeSignature(bool isValueType) => ToTypeSignature(); + /// public bool IsImportedInModule(ModuleDefinition module) => Signature?.IsImportedInModule(module) ?? false; diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index 5c0bfda0f..98b14d222 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -25,7 +25,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE.File/IPEFile.cs b/src/AsmResolver.PE.File/IPEFile.cs index 57e4e50a9..abc25a417 100644 --- a/src/AsmResolver.PE.File/IPEFile.cs +++ b/src/AsmResolver.PE.File/IPEFile.cs @@ -9,7 +9,7 @@ namespace AsmResolver.PE.File /// /// Provides a writable implementation of the interface. /// - public interface IPEFile : ISegmentReferenceResolver, IOffsetConverter + public interface IPEFile : ISegmentReferenceFactory, IOffsetConverter { /// /// When this PE file was read from the disk, gets the file path to the PE file. diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index d1dc0f2d7..6ce7616e2 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.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 ISegment? Contents 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/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index 058547040..a008308e1 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE/Code/CodeSegment.cs b/src/AsmResolver.PE/Code/CodeSegment.cs index eac4e00ef..b392e146e 100644 --- a/src/AsmResolver.PE/Code/CodeSegment.cs +++ b/src/AsmResolver.PE/Code/CodeSegment.cs @@ -56,6 +56,13 @@ public IList AddressFixups get; } = new List(); + /// + public override void UpdateOffsets(in RelocationParameters parameters) + { + ImageBase = parameters.ImageBase; + base.UpdateOffsets(in parameters); + } + /// public override uint GetPhysicalSize() => (uint) Code.Length; diff --git a/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs b/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs index bf5ce6ac0..e5afa4e88 100644 --- a/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs @@ -41,7 +41,7 @@ public void AddEntry(DebugDataEntry entry) public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _headers.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _headers.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _headers.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs b/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs index ec05a4438..98af4faf0 100644 --- a/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs +++ b/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs @@ -45,10 +45,10 @@ public ISegment? Contents public bool CanUpdateOffsets => Contents?.CanUpdateOffsets ?? false; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { if (Contents != null) - Contents.UpdateOffsets(newOffset, newRva); + Contents.UpdateOffsets(parameters); else throw new ArgumentNullException(nameof(Contents)); } diff --git a/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs b/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs index 44cfd21ef..fe345de97 100644 --- a/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs +++ b/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs @@ -73,7 +73,7 @@ private void AddIfPresent(ISegment? segment) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _segments.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs index 9c19fbc71..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/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..2d22d134a 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs @@ -153,7 +153,7 @@ public IList ExtraSections // Create body. var body = new CilRawFatMethodBody(flags, maxStack, localVarSigToken, code); - body.UpdateOffsets(fileOffset, rva); + body.UpdateOffsets(new RelocationParameters(fileOffset, rva)); // Read any extra sections. if (body.HasSections) @@ -172,18 +172,24 @@ public IList ExtraSections } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - Code.UpdateOffsets(newOffset + 12, newRva + 12); + base.UpdateOffsets(parameters); + + var current = parameters.WithAdvance(12); + + Code.UpdateOffsets(current); if (HasSections) { uint codeSize = Code.GetPhysicalSize(); - newOffset = (Code.Offset + codeSize).Align(4); - newRva = (Code.Rva + codeSize).Align(4); + current.Advance(codeSize); + current.Align(4); for (int i = 0; i < ExtraSections.Count; i++) - ExtraSections[i].UpdateOffsets(newOffset, newRva); + { + ExtraSections[i].UpdateOffsets(current); + current.Advance(ExtraSections[i].GetPhysicalSize()); + } } } diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs index 5c35165ad..852af652b 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs @@ -58,15 +58,15 @@ public CilRawTinyMethodBody(IReadableSegment code) uint codeSize = (uint) flag >> 2; var methodBody = new CilRawTinyMethodBody(reader.ReadSegment(codeSize)); - methodBody.UpdateOffsets(fileOffset, rva); + methodBody.UpdateOffsets(new RelocationParameters(fileOffset, rva)); return methodBody; } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - Code.UpdateOffsets(newOffset + 1, newRva + 1); + base.UpdateOffsets(parameters); + Code.UpdateOffsets(parameters.WithAdvance(1)); } /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs b/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs index b39a0ba3f..056f2b274 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs @@ -68,7 +68,7 @@ public BinaryStreamReader CreateReader() } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => Contents.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => Contents.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => Contents.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs index 5927c54b5..2c4d3364c 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs @@ -1,7 +1,7 @@ -using System; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Blob; using AsmResolver.PE.DotNet.Metadata.Guid; +using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.UserStrings; @@ -15,8 +15,17 @@ namespace AsmResolver.PE.DotNet.Metadata /// public class DefaultMetadataStreamReader : IMetadataStreamReader { + /// + /// Gets a default instance + /// + public static DefaultMetadataStreamReader Instance + { + get; + } = new(); + /// - public IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader header, + public IMetadataStream ReadStream(MetadataReaderContext context, + MetadataStreamHeader header, ref BinaryStreamReader reader) { switch (header.Name) @@ -37,6 +46,9 @@ public IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader 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/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/IMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs index 319459f43..01a4ed0c1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs @@ -14,6 +14,6 @@ public interface IMetadataStreamReader /// The header of the metadata stream. /// The input stream to read from. /// The read metadata stream. - IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader); + IMetadataStream ReadStream(MetadataReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader); } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs index cc1b88b5c..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/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..0fe56918b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs @@ -9,25 +9,29 @@ namespace AsmResolver.PE.DotNet.Metadata /// public class MetadataStreamList : LazyList { - private readonly PEReaderContext _context; + 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( - PEReaderContext context, + 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; @@ -48,11 +52,20 @@ 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); } } + /// + 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 new file mode 100644 index 000000000..4c4d7e002 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Tables; + +namespace AsmResolver.PE.DotNet.Metadata.Pdb +{ + /// + /// Represents the metadata stream containing Portable PDB debug data that is associated to a .NET module. + /// + public class PdbStream : SegmentBase, IMetadataStream + { + /// + /// The default name of a PDB stream, as described in the specification provided by Portable PDB v1.0. + /// + public const string DefaultName = "#Pdb"; + + /// + public string Name + { + get; + set; + } = DefaultName; + + /// + public virtual bool CanRead => false; + + /// + /// Gets the unique identifier representing the debugging metadata blob content. + /// + public byte[] Id + { + get; + } = new byte[20]; + + /// + /// Gets or sets the token of the entry point method, or 9 if not applicable. + /// + /// + /// This should be the same value as stored in the metadata header. + /// + public MetadataToken EntryPoint + { + get; + set; + } + + /// + /// Gets an array of row counts of every portable PDB table in the tables stream. + /// + public uint[] TypeSystemRowCounts + { + get; + } = new uint[(int) TableIndex.Max]; + + /// + /// Synchronizes the row counts stored in with the tables in the provided + /// tables stream. + /// + /// The tables stream to pull the data from. + public void UpdateRowCounts(TablesStream stream) + { + for (TableIndex i = 0; i < TableIndex.MaxTypeSystemTableIndex; i++) + { + if (i.IsValidTableIndex()) + TypeSystemRowCounts[(int) i] = (uint) stream.GetTable(i).Count; + } + } + + /// + /// Synchronizes the row counts stored in with the tables in the provided + /// tables stream row counts. + /// + /// The tables stream row counts to pull in. + public void UpdateRowCounts(uint[] rowCounts) + { + for (TableIndex i = 0; i < TableIndex.MaxTypeSystemTableIndex && (int) i < rowCounts.Length; i++) + { + if (i.IsValidTableIndex()) + TypeSystemRowCounts[(int) i] = rowCounts[(int) i]; + } + } + + /// + /// Computes the valid bitmask for the type system table rows referenced by this pdb stream. + /// + /// The bitmask. + public ulong ComputeReferencedTypeSystemTables() + { + ulong result = 0; + + for (int i = 0; i < TypeSystemRowCounts.Length; i++) + { + if (TypeSystemRowCounts[i] != 0) + result |= 1UL << i; + } + + return result; + } + + /// + public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException(); + + /// + public override uint GetPhysicalSize() + { + return 20 // ID + + sizeof(uint) // EntryPoint + + sizeof(ulong) // ReferencedTypeSystemTables + + 4 * (uint) TypeSystemRowCounts.Count(c => c != 0); // TypeSystemTableRows. + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + writer.WriteBytes(Id); + writer.WriteUInt32(EntryPoint.ToUInt32()); + writer.WriteUInt64(ComputeReferencedTypeSystemTables()); + + foreach (uint count in TypeSystemRowCounts) + { + if (count != 0) + writer.WriteUInt32(count); + } + } + + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs new file mode 100644 index 000000000..d6603f0b8 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs @@ -0,0 +1,63 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Pdb +{ + /// + /// Provides an implementation of a PDB stream that obtains GUIDs from a readable segment in a file. + /// + public class SerializedPdbStream : PdbStream + { + private readonly BinaryStreamReader _reader; + + /// + /// Creates a new PDB stream with the provided byte array as the raw contents of the stream. + /// + /// The raw contents of the stream. + public SerializedPdbStream(byte[] rawData) + : this(DefaultName, 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) + { + _reader = reader; + + Name = name; + Offset = reader.Offset; + Rva = reader.Rva; + + var headerReader = reader.Fork(); + + headerReader.ReadBytes(Id, 0, Id.Length); + EntryPoint = headerReader.ReadUInt32(); + + ulong mask = headerReader.ReadUInt64(); + for (int i = 0; i < 64; i++) + { + if (((mask >> i) & 1) != 0) + TypeSystemRowCounts[i] = headerReader.ReadUInt32(); + } + } + + /// + public override bool CanRead => true; + + /// + public override BinaryStreamReader CreateReader() => _reader.Fork(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs index 1c3ab7bb2..74cecd3da 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)); @@ -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/Strings/SerializedStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs index 55d2f9299..293562f36 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs @@ -65,22 +65,7 @@ public SerializedStringsStream(string name, in BinaryStreamReader reader) if (!_cachedStrings.TryGetValue(index, out var value) && index < _reader.Length) { var stringsReader = _reader.ForkRelative(index); - byte[] rawData = stringsReader.ReadBytesUntil(0); - - if (rawData.Length == 0) - { - value = Utf8String.Empty; - } - else - { - // Trim off null terminator byte if its present. - int actualLength = rawData.Length; - if (rawData[actualLength - 1] == 0) - actualLength--; - - value = new Utf8String(rawData, 0, actualLength); - } - + value = stringsReader.ReadUtf8String(); _cachedStrings[index] = value; } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs index 27f1fbc6a..8817f38dc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs @@ -8,13 +8,13 @@ public enum CodedIndex /// /// Indicates the index is an index to a member in either the TypeRef, TypeDef or TypeSpec table. /// - TypeDefOrRef = 45, - + TypeDefOrRef = ColumnType.TypeDefOrRef, + /// /// Indicates the index is an index to a member in either the Field, Parameter or Property table. /// HasConstant, - + /// /// Indicates the index is an index to a member in one of the following tables: /// MethodDef, Field, TypeRef, TypeDef, Parameter, InterfaceImpl, MemberRef, Module, DeclSecurity, Property, Event, @@ -22,57 +22,66 @@ public enum CodedIndex /// GenericParamConstraint or MethodSpec. /// HasCustomAttribute, - + /// /// Indicates the index is an index to a member in either the Field or Parameter table. /// HasFieldMarshal, - + /// /// Indicates the index is an index to a member in either the TypeDef, MethodDef or Assembly table. /// HasDeclSecurity, - + /// /// Indicates the index is an index to a member in either the TypeDef, TypeRef, ModuleRef, MethodDef or TypeSpec /// table. /// MemberRefParent, - + /// /// Indicates the index is an index to a member in either the Event or Property table. /// HasSemantics, - + /// /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. /// MethodDefOrRef, - + /// /// Indicates the index is an index to a member in either the Field or MethodDef table. /// MemberForwarded, - + /// /// Indicates the index is an index to a member in either the File, AssemblyRef or ExportedType table. /// Implementation, - + /// - /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. + /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. /// CustomAttributeType, - + /// /// Indicates the index is an index to a member in either the Module, ModuleRef, AssemblyRef or TypeRef table. /// ResolutionScope, - + /// /// Indicates the index is an index to a member in either the TypeDef or MethodDef table. /// TypeOrMethodDef, + + /// + /// Indicates the index is an index to a member in one of the following tables: + /// MethodDef, Field, TypeRef, TypeDef, Parameter, InterfaceImpl, MemberRef, Module, DeclSecurity, Property, + /// Event, StandAloneSig, ModuleRef, TypeSpec, Assembly, AssemblyRef, File, ExportedType, ManifestResource, + /// GenericParam, GenericParamConstraint, MethodSpec, Document, LocalScope, LocalVariable, LocalConstant, + /// or ImportScope + /// + HasCustomDebugInformation } - -} \ No newline at end of file + +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs index c24876da4..5e794615d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs @@ -8,6 +8,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// public enum ColumnType { + // Normal type system indices (in sync with TableIndex). Module = 0, TypeRef = 1, TypeDef = 2, @@ -53,7 +54,18 @@ public enum ColumnType GenericParam = 42, MethodSpec = 43, GenericParamConstraint = 44, - + + // PortablePDB indices (in sync with TableIndex). + Document = 0x30, + MethodDebugInformation = 0x31, + LocalScope = 0x32, + LocalVariable = 0x33, + LocalConstant = 0x34, + ImportScope = 0x35, + StateMachineMethod = 0x36, + CustomDebugInformation = 0x37, + + // Coded indices (in sync with CodedIndex). TypeDefOrRef, HasConstant, HasCustomAttribute, @@ -67,13 +79,16 @@ public enum ColumnType CustomAttributeType, ResolutionScope, TypeOrMethodDef, - - String, + HasCustomDebugInformation, + + // Heap indices. + String, Blob, Guid, - + + // Primitives. Byte = 0x8000001, UInt16 = 0x8000002, UInt32 = 0x8000004, } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/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 @@ IMetadataRow this[int index] 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/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/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 TRow this[int index] set => Rows[index] = value; } + /// + public bool IsSorted + { + get; + set; + } + /// IMetadataRow IMetadataTable.this[int index] { 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/Rows/CustomDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs new file mode 100644 index 000000000..1959a9233 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB custom debug information metadata table. + /// + public struct CustomDebugInformationRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB custom debug information metadata table. + /// + /// + /// A coded index defining the member that this debug information is associated to. + /// + /// + /// An index into the GUID stream referencing the type of debug data that is stored in this record. + /// + /// + /// An index into the blob stream referencing the data of the record. + /// + public CustomDebugInformationRow(uint parent, uint kind, uint value) + { + Parent = parent; + Kind = kind; + Value = value; + } + + /// + public TableIndex TableIndex => TableIndex.CustomDebugInformation; + + /// + public int Count => 3; + + /// + public uint this[int index] => index switch + { + 0 => Parent, + 1 => Kind, + 2 => Value, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets a coded index defining the member that this debug information is associated to. + /// + public uint Parent + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the type of debug data that is stored in this record. + /// + public uint Kind + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the data of the record. + /// + public uint Value + { + get; + set; + } + + /// + /// Reads a single Portable PDB custom debug information row from an input stream. + /// + /// The input stream. + /// The layout of the custom debug information table. + /// The row. + public static CustomDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new CustomDebugInformationRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Parent, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Kind, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(Value, (IndexSize) layout.Columns[2].Size); + } + + /// + /// Determines whether this row is considered equal to the provided custom debug information row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(CustomDebugInformationRow other) + { + return Parent == other.Parent && Kind == other.Kind && Value == other.Value; + } + + /// + public override bool Equals(object? obj) + { + return obj is CustomDebugInformationRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Parent; + hashCode = (hashCode * 397) ^ (int) Kind; + hashCode = (hashCode * 397) ^ (int) Value; + return hashCode; + } + } + + /// + public override string ToString() => $"({Parent:X8}, {Kind:X8}, {Value:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs new file mode 100644 index 000000000..0d8d8378b --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB Document metadata table. + /// + public struct DocumentRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Document metadata table. + /// + /// The index into the blob stream referencing the name of the document. + /// The index into the GUID stream referencing the hash algorithm identifier. + /// The index into the blob stream referencing the hash of the document. + /// The index into the GUID stream referencing the language identifier. + public DocumentRow(uint name, uint hashAlgorithm, uint hash, uint language) + { + Name = name; + HashAlgorithm = hashAlgorithm; + Hash = hash; + Language = language; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 4; + + /// + public uint this[int index] => index switch + { + 0 => Name, + 1 => HashAlgorithm, + 2 => Hash, + 3 => Language, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the blob stream referencing the name of the document. + /// + public uint Name + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the hash algorithm identifier. + /// + public uint HashAlgorithm + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the hash of the document. + /// + public uint Hash + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the language identifier. + /// + public uint Language + { + get; + set; + } + + /// + /// Reads a single Portable PDB Document row from an input stream. + /// + /// The input stream. + /// The layout of the document table. + /// The row. + public static DocumentRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new DocumentRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size), + reader.ReadIndex((IndexSize) layout.Columns[3].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Name, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(HashAlgorithm, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(Hash, (IndexSize) layout.Columns[2].Size); + writer.WriteIndex(Language, (IndexSize) layout.Columns[3].Size); + } + + /// + /// Determines whether this row is considered equal to the provided document row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(DocumentRow other) + { + return Name == other.Name + && HashAlgorithm == other.HashAlgorithm + && Hash == other.Hash + && Language == other.Language; + } + + /// + public override bool Equals(object? obj) => obj is DocumentRow other && Equals(other); + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Name; + hashCode = (hashCode * 397) ^ (int) HashAlgorithm; + hashCode = (hashCode * 397) ^ (int) Hash; + hashCode = (hashCode * 397) ^ (int) Language; + return hashCode; + } + } + + /// + public override string ToString() => $"({Name:X8}, {HashAlgorithm:X8}, {Hash:X8}, {Language:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs index 97d7b6b92..d99f13746 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs @@ -17,10 +17,10 @@ public struct FieldRvaRow : IMetadataRow /// The input stream. /// The layout of the field RVA table. /// The row. - public static FieldRvaRow FromReader(PEReaderContext context, ref BinaryStreamReader reader, TableLayout layout) + public static FieldRvaRow FromReader(MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout) { return new FieldRvaRow( - context.File.GetReferenceToRva(reader.ReadUInt32()), + context.ReferenceFactory.GetReferenceToRva(reader.ReadUInt32()), reader.ReadIndex((IndexSize) layout.Columns[1].Size)); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs new file mode 100644 index 000000000..6badacd1c --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB import scope metadata table. + /// + public struct ImportScopeRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB import scope metadata table. + /// + /// + /// An index into the import parent scope defining the parent scope, or 0 if it is the root scope. + /// + /// + /// An index into the blob stream referencing the imports that this scope defines. + /// + public ImportScopeRow(uint parent, uint imports) + { + Parent = parent; + Imports = imports; + } + + /// + public TableIndex TableIndex => TableIndex.ImportScope; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Parent, + 1 => Imports, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the import parent scope defining the parent scope, or 0 if it is the root scope. + /// + public uint Parent + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the imports that this scope defines. + /// + public uint Imports + { + get; + set; + } + /// + /// Reads a single Portable PDB import scope row from an input stream. + /// + /// The input stream. + /// The layout of the import scope table. + /// The row. + public static ImportScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new ImportScopeRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Parent, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Imports, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided import scope row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(ImportScopeRow other) + { + return Parent == other.Parent && Imports == other.Imports; + } + + /// + public override bool Equals(object? obj) + { + return obj is ImportScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Parent * 397) ^ (int) Imports; + } + } + + /// + public override string ToString() => $"({Parent:X8}, {Imports:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs new file mode 100644 index 000000000..553195366 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local constant metadata table. + /// + public struct LocalConstantRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Constant metadata table. + /// + /// An index into the strings stream referencing the name of the constant. + /// An index into the blob stream referencing the signature of the constant. + public LocalConstantRow(uint name, uint signature) + { + Name = name; + Signature = signature; + } + + /// + public TableIndex TableIndex => TableIndex.LocalConstant; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Name, + 1 => Signature, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the strings stream referencing the name of the constant. + /// + public uint Name + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the signature of the constant. + /// + public uint Signature + { + get; + set; + } + + /// + /// Reads a single Portable PDB local constant row from an input stream. + /// + /// The input stream. + /// The layout of the local constant table. + /// The row. + public static LocalConstantRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalConstantRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Name, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Signature, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided local constant row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalConstantRow other) + { + return Name == other.Name && Signature == other.Signature; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalConstantRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Name * 397) ^ (int) Signature; + } + } + + /// + public override string ToString() => $"({Name:X8}, {Signature:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs new file mode 100644 index 000000000..9fa9c387e --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local scope metadata table. + /// + public struct LocalScopeRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Scope metadata table. + /// + /// An index into the method table that defines the scope. + /// An index into the import scope table that defines the scope. + /// An index into the local variable table referencing the first local variable in the method. + /// An index into the local constant table referencing the first constant in the method. + /// The starting CIL offset of the scope. + /// The number of CIL bytes the scope spans. + public LocalScopeRow(uint method, uint importScope, uint variableList, uint constantList, uint startOffset, uint length) + { + Method = method; + ImportScope = importScope; + VariableList = variableList; + ConstantList = constantList; + StartOffset = startOffset; + Length = length; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 6; + + /// + public uint this[int index] => index switch + { + 0 => Method, + 1 => ImportScope, + 2 => VariableList, + 3 => ConstantList, + 4 => StartOffset, + 5 => Length, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the method table that defines the scope. + /// + public uint Method + { + get; + set; + } + + /// + /// Gets or sets an index into the import scope table that defines the scope. + /// + public uint ImportScope + { + get; + set; + } + + /// + /// Gets or sets an index into the local variable table referencing the first local variable in the method. + /// + public uint VariableList + { + get; + set; + } + + /// + /// Gets or sets an index into the local constant table referencing the first constant in the method. + /// + public uint ConstantList + { + get; + set; + } + + /// + /// Gets or sets The starting CIL offset of the scope. + /// + public uint StartOffset + { + get; + set; + } + + /// + /// Gets or sets the number of CIL bytes the scope spans. + /// + public uint Length + { + get; + set; + } + + /// + /// Reads a single Portable PDB local scope row from an input stream. + /// + /// The input stream. + /// The layout of the local socpe table. + /// The row. + public static LocalScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalScopeRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size), + reader.ReadIndex((IndexSize) layout.Columns[3].Size), + reader.ReadUInt32(), + reader.ReadUInt32()); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Method, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(ImportScope, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(VariableList, (IndexSize) layout.Columns[2].Size); + writer.WriteIndex(ConstantList, (IndexSize) layout.Columns[3].Size); + writer.WriteUInt32(StartOffset); + writer.WriteUInt32(Length); + } + + /// + /// Determines whether this row is considered equal to the provided local scope row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalScopeRow other) + { + return Method == other.Method + && ImportScope == other.ImportScope + && VariableList == other.VariableList + && ConstantList == other.ConstantList + && StartOffset == other.StartOffset + && Length == other.Length; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Method; + hashCode = (hashCode * 397) ^ (int) ImportScope; + hashCode = (hashCode * 397) ^ (int) VariableList; + hashCode = (hashCode * 397) ^ (int) ConstantList; + hashCode = (hashCode * 397) ^ (int) StartOffset; + hashCode = (hashCode * 397) ^ (int) Length; + return hashCode; + } + } + + /// + public override string ToString() + { + return $"({Method:X8}, {ImportScope:X8}, {VariableList:X8}, {ConstantList:X8}, {StartOffset:X8}, {Length:X8})"; + } + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs new file mode 100644 index 000000000..503f2f555 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs @@ -0,0 +1,16 @@ +using System; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Provides members defining all possible flags that can be assigned to a local variable. + /// + [Flags] + public enum LocalVariableAttributes : ushort + { + /// + /// Indicates the local variable should be hidden in a debugger view. + /// + DebuggerHidden = 0x0001 + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs new file mode 100644 index 000000000..1c27f04f0 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local variable metadata table. + /// + public struct LocalVariableRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Variable metadata table. + /// + /// The attributes associated to the local variable. + /// The index of the local variable. + /// An index into the strings stream referencing the name of the local variable. + public LocalVariableRow(LocalVariableAttributes attributes, ushort index, uint name) + { + Attributes = attributes; + Index = index; + Name = name; + } + + /// + public TableIndex TableIndex => TableIndex.LocalVariable; + + /// + public int Count => 3; + + /// + public uint this[int index] => index switch + { + 0 => (uint) Attributes, + 1 => Index, + 2 => Name, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets the attributes associated to the local variable. + /// + public LocalVariableAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the index of the local variable. + /// + public ushort Index + { + get; + set; + } + + /// + /// Gets or sets an index into the strings stream referencing the name of the local variable. + /// + public uint Name + { + get; + set; + } + + /// + /// Reads a single Portable PDB local variable row from an input stream. + /// + /// The input stream. + /// The layout of the local variable table. + /// The row. + public static LocalVariableRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalVariableRow( + (LocalVariableAttributes) reader.ReadUInt16(), + reader.ReadUInt16(), + reader.ReadIndex((IndexSize) layout.Columns[2].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16(Index); + writer.WriteIndex(Name, (IndexSize) layout.Columns[2].Size); + } + + /// + /// Determines whether this row is considered equal to the provided local variable row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalVariableRow other) + { + return Attributes == other.Attributes + && Index == other.Index + && Name == other.Name; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Attributes; + hashCode = (hashCode * 397) ^ Index; + hashCode = (hashCode * 397) ^ (int) Name; + return hashCode; + } + } + + /// + public override string ToString() => $"({(ushort) Attributes:X4}, {Index:X4}, {Name:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs new file mode 100644 index 000000000..9cfbb821b --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB Document metadata table. + /// + public struct MethodDebugInformationRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Method Debug Information metadata table. + /// + /// + /// The index into the Document table referencing the document that declares the method. + /// + /// + /// The index into the blob stream referencing an array of sequence points that make up the method. + /// + public MethodDebugInformationRow(uint document, uint sequencePoints) + { + Document = document; + SequencePoints = sequencePoints; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Document, + 1 => SequencePoints, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the Document table referencing the document that declares the method, or 0 + /// if the method does not have sequence points or spans multiple documents. + /// + public uint Document + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing an array of sequence points that make up the method, + /// or 0 if no sequence points are available. + /// + public uint SequencePoints + { + get; + set; + } + + /// + /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// + /// The input stream. + /// The layout of the method debug information table. + /// The row. + public static MethodDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new MethodDebugInformationRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Document, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(SequencePoints, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided method debug information row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(MethodDebugInformationRow other) + { + return Document == other.Document && SequencePoints == other.SequencePoints; + } + + /// + public override bool Equals(object? obj) + { + return obj is MethodDebugInformationRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Document * 397) ^ (int) SequencePoints; + } + } + + /// + public override string ToString() => $"({Document:X8}, {SequencePoints:X8})"; + + /// + public IEnumerator GetEnumerator() + { + return new MetadataRowColumnEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs index 2b6df546f..6cd6e5481 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs @@ -17,10 +17,10 @@ public struct MethodDefinitionRow : IMetadataRow /// The input stream. /// The layout of the method definition table. /// The row. - public static MethodDefinitionRow FromReader(PEReaderContext context, ref BinaryStreamReader reader, TableLayout layout) + public static MethodDefinitionRow FromReader(MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout) { return new MethodDefinitionRow( - context.File.GetReferenceToRva(reader.ReadUInt32()), + context.ReferenceFactory.GetReferenceToRva(reader.ReadUInt32()), (MethodImplAttributes) reader.ReadUInt16(), (MethodAttributes) reader.ReadUInt16(), reader.ReadIndex((IndexSize) layout.Columns[3].Size), diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs new file mode 100644 index 000000000..914125195 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB state machine method metadata table. + /// + public struct StateMachineMethodRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB state machine method metadata table. + /// + /// + /// An index into the method table referencing the MoveNext method of an async state machine. + /// + /// + /// An index into the method table referencing the kickoff method of an async state machine. + /// + public StateMachineMethodRow(uint moveNextMethod, uint kickoffMethod) + { + MoveNextMethod = moveNextMethod; + KickoffMethod = kickoffMethod; + } + + /// + public TableIndex TableIndex => TableIndex.StateMachineMethod; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => MoveNextMethod, + 1 => KickoffMethod, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the method table referencing the MoveNext method of an async state machine. + /// + public uint MoveNextMethod + { + get; + set; + } + + /// + /// Gets or sets an index into the method table referencing the kickoff method of an async state machine. + /// + public uint KickoffMethod + { + get; + set; + } + + /// + /// Reads a single Portable PDB state machine method row from an input stream. + /// + /// The input stream. + /// The layout of the state machine method table. + /// The row. + public static StateMachineMethodRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new StateMachineMethodRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(MoveNextMethod, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(KickoffMethod, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided state machine method row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(StateMachineMethodRow other) + { + return MoveNextMethod == other.MoveNextMethod && KickoffMethod == other.KickoffMethod; + } + + /// + public override bool Equals(object? obj) + { + return obj is StateMachineMethodRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) MoveNextMethod * 397) ^ (int) KickoffMethod; + } + } + + /// + public override string ToString() => $"({MoveNextMethod:X8}, {KickoffMethod:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs index 0529b40f3..b39575f70 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs @@ -27,7 +27,7 @@ public class SerializedMetadataTable : MetadataTable /// The input stream. /// The layout of the table. public delegate TRow ReadRowExtendedDelegate( - PEReaderContext context, + MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout); @@ -42,9 +42,15 @@ public delegate TRow ReadRowExtendedDelegate( /// The input stream. /// The index of the table. /// The layout of the table. + /// Indicates the table is sorted or not. /// The method to use for reading each row in the table. - public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableIndex, TableLayout originalLayout, ReadRowDelegate readRow) - : base(tableIndex, originalLayout) + public SerializedMetadataTable( + in BinaryStreamReader reader, + TableIndex tableIndex, + TableLayout originalLayout, + bool isSorted, + ReadRowDelegate readRow) + : base(tableIndex, originalLayout, isSorted) { _reader = reader; _originalLayout = originalLayout; @@ -59,14 +65,16 @@ public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableInd /// The input stream. /// The index of the table. /// The layout of the table. + /// Indicates the table is sorted or not. /// The method to use for reading each row in the table. public SerializedMetadataTable( - PEReaderContext context, + MetadataReaderContext context, in BinaryStreamReader reader, TableIndex tableIndex, TableLayout originalLayout, + bool isSorted, ReadRowExtendedDelegate readRow) - : this(reader, tableIndex, originalLayout, + : this(reader, tableIndex, originalLayout, isSorted, (ref BinaryStreamReader r, TableLayout l) => readRow(context, ref r, l)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs index f2a910eb8..8608952fc 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,24 +10,34 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Provides an implementation of a tables stream that obtains tables from a readable segment in a file. /// - public class SerializedTableStream : TablesStream + public class SerializedTableStream : TablesStream, ILazyMetadataStream { - private readonly PEReaderContext _context; + private readonly MetadataReaderContext _context; private readonly BinaryStreamReader _reader; private readonly ulong _validMask; private readonly ulong _sortedMask; private readonly uint[] _rowCounts; - private readonly IndexSize[] _indexSizes; private readonly uint _headerSize; private bool _tablesInitialized; + /// + /// Same as but may contain row counts from an external tables stream. + /// This is required for metadata directories containing Portable PDB debug data. + /// + private uint[]? _combinedRowCounts; + + /// + /// Contains the initial sizes of every column type. + /// + private IndexSize[]? _indexSizes; + /// /// Creates a new tables stream with the provided byte array as the raw contents of the stream. /// /// The reader context. /// The name of the stream. /// The raw contents of the stream. - public SerializedTableStream(PEReaderContext context, string name, byte[] rawData) + public SerializedTableStream(MetadataReaderContext context, string name, byte[] rawData) : this(context, name, ByteArrayDataSource.CreateReader(rawData)) { } @@ -37,7 +48,7 @@ public SerializedTableStream(PEReaderContext context, string name, byte[] rawDat /// The reader context. /// The name of the stream. /// The raw contents of the stream. - public SerializedTableStream(PEReaderContext context, string name, in BinaryStreamReader reader) + public SerializedTableStream(MetadataReaderContext context, string name, in BinaryStreamReader reader) { Name = name ?? throw new ArgumentNullException(nameof(name)); _context = context ?? throw new ArgumentNullException(nameof(context)); @@ -54,15 +65,12 @@ public SerializedTableStream(PEReaderContext context, string name, in BinaryStre Log2LargestRid = headerReader.ReadByte(); _validMask = headerReader.ReadUInt64(); _sortedMask = headerReader.ReadUInt64(); - _rowCounts = ReadRowCounts(ref headerReader); if (HasExtraData) ExtraData = headerReader.ReadUInt32(); _headerSize = headerReader.RelativeOffset; - - _indexSizes = InitializeIndexSizes(); } /// @@ -73,19 +81,55 @@ public SerializedTableStream(PEReaderContext context, string name, in BinaryStre private uint[] ReadRowCounts(ref BinaryStreamReader reader) { - const TableIndex maxTableIndex = TableIndex.GenericParamConstraint; + uint[] result = new uint[(int) TableIndex.Max]; - var result = new uint[(int) maxTableIndex + 1 ]; - for (TableIndex i = 0; i <= maxTableIndex; i++) - result[(int) i] = HasTable(_validMask, i) ? reader.ReadUInt32() : 0; + for (TableIndex i = 0; i < TableIndex.Max; i++) + { + result[(int) i] = HasTable(_validMask, i) + ? reader.ReadUInt32() + : 0; + } return result; } + /// + public void Initialize(IMetadata parentMetadata) + { + if (parentMetadata.TryGetStream(out PdbStream? pdbStream)) + { + // Metadata that contains a PDB stream should use the row counts provided in the pdb stream + // for computing the size of a column. + _combinedRowCounts = new uint[_rowCounts.Length]; + ExternalRowCounts = new uint[(int) TableIndex.Document]; + + for (int i = 0; i < (int) TableIndex.Document; i++) + { + _combinedRowCounts[i] = pdbStream.TypeSystemRowCounts[i]; + ExternalRowCounts[i] = pdbStream.TypeSystemRowCounts[i]; + } + + for (int i = (int) TableIndex.Document; i < (int) TableIndex.Max; i++) + _combinedRowCounts[i] = _rowCounts[i]; + + } + else + { + // Otherwise, just use the original row counts array. + _combinedRowCounts = _rowCounts; + } + + _indexSizes = InitializeIndexSizes(); + } + /// protected override uint GetColumnSize(ColumnType columnType) { - if (_tablesInitialized || (int) columnType >= _indexSizes.Length) + if (_tablesInitialized) + return base.GetColumnSize(columnType); + if (_indexSizes is null) + throw new InvalidOperationException("Serialized tables stream is not fully initialized yet."); + if ((int) columnType >= _indexSizes.Length) return base.GetColumnSize(columnType); return (uint) _indexSizes[(int) columnType]; } @@ -149,6 +193,15 @@ private IndexSize[] InitializeIndexSizes() // TypeOrMethodDef GetCodedIndexSize(TableIndex.TypeDef, TableIndex.Method), + + // HasCustomDebugInformation + GetCodedIndexSize(TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, + TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, + TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, + TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, + TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, + TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) }); return result.ToArray(); @@ -156,19 +209,22 @@ private IndexSize[] InitializeIndexSizes() private IndexSize GetCodedIndexSize(params TableIndex[] tables) { + if (_combinedRowCounts is null) + throw new InvalidOperationException("Serialized tables stream is not fully initialized yet."); + int tableIndexBitCount = (int) Math.Ceiling(Math.Log(tables.Length, 2)); int maxSmallTableMemberCount = ushort.MaxValue >> tableIndexBitCount; - return tables.Select(t => _rowCounts[(int) t]).All(c => c < maxSmallTableMemberCount) + return tables.Select(t => _combinedRowCounts[(int) t]).All(c => c < maxSmallTableMemberCount) ? IndexSize.Short : IndexSize.Long; } /// - protected override IList GetTables() + protected override IList GetTables() { uint offset = _headerSize; - var tables = new IMetadataTable[] + var tables = new IMetadataTable?[] { CreateNextTable(TableIndex.Module, ref offset, ModuleDefinitionRow.FromReader), CreateNextTable(TableIndex.TypeRef, ref offset, TypeReferenceRow.FromReader), @@ -215,6 +271,17 @@ protected override IList GetTables() CreateNextTable(TableIndex.GenericParam, ref offset, GenericParameterRow.FromReader), CreateNextTable(TableIndex.MethodSpec, ref offset, MethodSpecificationRow.FromReader), CreateNextTable(TableIndex.GenericParamConstraint, ref offset, GenericParameterConstraintRow.FromReader), + null, + null, + null, + CreateNextTable(TableIndex.Document, ref offset, DocumentRow.FromReader), + CreateNextTable(TableIndex.MethodDebugInformation, ref offset, MethodDebugInformationRow.FromReader), + CreateNextTable(TableIndex.LocalScope, ref offset, LocalScopeRow.FromReader), + CreateNextTable(TableIndex.LocalVariable, ref offset, LocalVariableRow.FromReader), + CreateNextTable(TableIndex.LocalConstant, ref offset, LocalConstantRow.FromReader), + CreateNextTable(TableIndex.ImportScope, ref offset, ImportScopeRow.FromReader), + CreateNextTable(TableIndex.StateMachineMethod, ref offset, StateMachineMethodRow.FromReader), + CreateNextTable(TableIndex.CustomDebugInformation, ref offset, CustomDebugInformationRow.FromReader), }; _tablesInitialized = true; return tables; @@ -239,6 +306,7 @@ private SerializedMetadataTable CreateNextTable( CreateNextRawTableReader(index, ref offset), index, TableLayouts[(int) index], + IsSorted(_sortedMask, index), readRow); } @@ -253,6 +321,7 @@ private SerializedMetadataTable CreateNextTable( CreateNextRawTableReader(index, ref offset), index, TableLayouts[(int) index], + IsSorted(_sortedMask, index), readRow); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs index d9ca9e3bc..8ee7dd66b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs @@ -53,8 +53,21 @@ public enum TableIndex : byte GenericParam = 42, MethodSpec = 43, GenericParamConstraint = 44, - Max = GenericParamConstraint + 1, + + MaxTypeSystemTableIndex = GenericParamConstraint, + + Document = 0x30, + MethodDebugInformation = 0x31, + LocalScope = 0x32, + LocalVariable = 0x33, + LocalConstant = 0x34, + ImportScope = 0x35, + StateMachineMethod = 0x36, + CustomDebugInformation = 0x37, + + Max = CustomDebugInformation + 1, String = 0x70 } + } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs new file mode 100644 index 000000000..e9573dfb2 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs @@ -0,0 +1,19 @@ +namespace AsmResolver.PE.DotNet.Metadata.Tables; + +/// +/// Provides extension methods to the enumeration. +/// +public static class TableIndexExtensions +{ + /// + /// Determines whether the provided index is a valid table index. + /// + /// The index. + /// true if valid, false otherwise. + public static bool IsValidTableIndex(this TableIndex index) + { + return index is >= TableIndex.Module and <= TableIndex.GenericParamConstraint + or >= TableIndex.Document and <= TableIndex.CustomDebugInformation + or TableIndex.String; + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs new file mode 100644 index 000000000..877d34129 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs @@ -0,0 +1,474 @@ +using System.Collections.Generic; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; + +namespace AsmResolver.PE.DotNet.Metadata.Tables +{ + public partial class TablesStream + { + /// + /// Obtains the collection of tables in the tables stream. + /// + /// The tables, including empty tables if there are any. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetTables() + { + var layouts = TableLayouts; + return new IMetadataTable?[] + { + new MetadataTable(TableIndex.Module, layouts[0]), + new MetadataTable(TableIndex.TypeRef, layouts[1]), + new MetadataTable(TableIndex.TypeDef, layouts[2]), + new MetadataTable(TableIndex.FieldPtr, layouts[3]), + new MetadataTable(TableIndex.Field, layouts[4]), + new MetadataTable(TableIndex.Method, layouts[5]), + new MetadataTable(TableIndex.Method, layouts[6]), + new MetadataTable(TableIndex.ParamPtr, layouts[7]), + new MetadataTable(TableIndex.Param, layouts[8]), + new MetadataTable(TableIndex.InterfaceImpl, layouts[9], true), + new MetadataTable(TableIndex.MemberRef, layouts[10]), + new MetadataTable(TableIndex.Constant, layouts[11], true), + new MetadataTable(TableIndex.CustomAttribute, layouts[12], true), + new MetadataTable(TableIndex.FieldMarshal, layouts[13], true), + new MetadataTable(TableIndex.DeclSecurity, layouts[14], true), + new MetadataTable(TableIndex.ClassLayout, layouts[15], true), + new MetadataTable(TableIndex.FieldLayout, layouts[16], true), + new MetadataTable(TableIndex.StandAloneSig, layouts[17]), + new MetadataTable(TableIndex.EventMap, layouts[18]), + new MetadataTable(TableIndex.EventPtr, layouts[19]), + new MetadataTable(TableIndex.Event, layouts[20]), + new MetadataTable(TableIndex.PropertyMap, layouts[21]), + new MetadataTable(TableIndex.PropertyPtr, layouts[22]), + new MetadataTable(TableIndex.Property, layouts[23]), + new MetadataTable(TableIndex.MethodSemantics, layouts[24], true), + new MetadataTable(TableIndex.MethodImpl, layouts[25], true), + new MetadataTable(TableIndex.ModuleRef, layouts[26]), + new MetadataTable(TableIndex.TypeSpec, layouts[27]), + new MetadataTable(TableIndex.ImplMap, layouts[28], true), + new MetadataTable(TableIndex.FieldRva, layouts[29], true), + new MetadataTable(TableIndex.EncLog, layouts[30]), + new MetadataTable(TableIndex.EncMap, layouts[31]), + new MetadataTable(TableIndex.Assembly, layouts[32]), + new MetadataTable(TableIndex.AssemblyProcessor, layouts[33]), + new MetadataTable(TableIndex.AssemblyOS, layouts[34]), + new MetadataTable(TableIndex.AssemblyRef, layouts[35]), + new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[36]), + new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[37]), + new MetadataTable(TableIndex.File, layouts[38]), + new MetadataTable(TableIndex.ExportedType, layouts[39]), + new MetadataTable(TableIndex.ManifestResource, layouts[40]), + new MetadataTable(TableIndex.NestedClass, layouts[41], true), + new MetadataTable(TableIndex.GenericParam, layouts[42], true), + new MetadataTable(TableIndex.MethodSpec, layouts[43]), + new MetadataTable(TableIndex.GenericParamConstraint, layouts[44], true), + null, + null, + null, + new MetadataTable(TableIndex.Document, layouts[48]), + new MetadataTable(TableIndex.MethodDebugInformation, layouts[49]), + new MetadataTable(TableIndex.LocalScope, layouts[50], true), + new MetadataTable(TableIndex.LocalVariable, layouts[51]), + new MetadataTable(TableIndex.LocalConstant, layouts[52]), + new MetadataTable(TableIndex.ImportScope, layouts[53]), + new MetadataTable(TableIndex.StateMachineMethod, layouts[54]), + new MetadataTable(TableIndex.CustomDebugInformation, layouts[55], true), + }; + } + + private Dictionary CreateIndexEncoders() + { + return new() + { + [CodedIndex.TypeDefOrRef] = new IndexEncoder(this, + TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec), + + [CodedIndex.HasConstant] = new(this, + TableIndex.Field, TableIndex.Param, TableIndex.Property), + + [CodedIndex.HasCustomAttribute] = new(this, + TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, + TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, + TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, + TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, + TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec), + + [CodedIndex.HasFieldMarshal] = new(this, + TableIndex.Field, TableIndex.Param), + + [CodedIndex.HasDeclSecurity] = new(this, + TableIndex.TypeDef, TableIndex.Method, TableIndex.Assembly), + + [CodedIndex.MemberRefParent] = new(this, + TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef, TableIndex.Method, + TableIndex.TypeSpec), + + [CodedIndex.HasSemantics] = new(this, + TableIndex.Event, TableIndex.Property), + + [CodedIndex.MethodDefOrRef] = new(this, + TableIndex.Method, TableIndex.MemberRef), + + [CodedIndex.MemberForwarded] = new(this, + TableIndex.Field, TableIndex.Method), + + [CodedIndex.Implementation] = new(this, + TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType), + + [CodedIndex.CustomAttributeType] = new(this, + 0, 0, TableIndex.Method, TableIndex.MemberRef, 0), + + [CodedIndex.ResolutionScope] = new(this, + TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef), + + [CodedIndex.TypeOrMethodDef] = new(this, + TableIndex.TypeDef, TableIndex.Method), + + [CodedIndex.HasCustomDebugInformation] = new(this, + TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, TableIndex.Param, + TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, TableIndex.DeclSecurity, + TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, TableIndex.ModuleRef, + TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, TableIndex.File, + TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, + TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) + }; + } + + /// + /// Gets an ordered collection of the current table layouts. + /// + /// The table layouts. + protected TableLayout[] GetTableLayouts() => new[] + { + // Module + new TableLayout( + new ColumnLayout("Generation", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Mvid", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("EncId", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("EncBaseId", ColumnType.Guid, GuidIndexSize)), + + // TypeRef + new TableLayout( + new ColumnLayout("ResolutionScope", ColumnType.ResolutionScope, + GetColumnSize(ColumnType.ResolutionScope)), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.Guid, StringIndexSize)), + + // TypeDef + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), + new ColumnLayout("Extends", ColumnType.TypeDefOrRef, + GetColumnSize(ColumnType.TypeDefOrRef)), + new ColumnLayout("FieldList", ColumnType.Field, GetColumnSize(ColumnType.Field)), + new ColumnLayout("MethodList", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // FieldPtr + new TableLayout( + new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), + + // Field + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // MethodPtr + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // Method + new TableLayout( + new ColumnLayout("RVA", ColumnType.UInt32), + new ColumnLayout("ImplFlags", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("ParamList", ColumnType.Param, GetColumnSize(ColumnType.Param))), + + // ParamPtr + new TableLayout( + new ColumnLayout("Parameter", ColumnType.Param, GetColumnSize(ColumnType.Param))), + + // Parameter + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Sequence", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize)), + + // InterfaceImpl + new TableLayout( + new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("Interface", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // MemberRef + new TableLayout( + new ColumnLayout("Parent", ColumnType.MemberRefParent, GetColumnSize(ColumnType.MemberRefParent)), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // Constant + new TableLayout( + new ColumnLayout("Type", ColumnType.Byte), + new ColumnLayout("Padding", ColumnType.Byte), + new ColumnLayout("Parent", ColumnType.HasConstant, GetColumnSize(ColumnType.HasConstant)), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + + // CustomAttribute + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasCustomAttribute, + GetColumnSize(ColumnType.HasCustomAttribute)), + new ColumnLayout("Type", ColumnType.CustomAttributeType, + GetColumnSize(ColumnType.CustomAttributeType)), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + + // FieldMarshal + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasFieldMarshal, GetColumnSize(ColumnType.HasFieldMarshal)), + new ColumnLayout("NativeType", ColumnType.Blob, BlobIndexSize)), + + // DeclSecurity + new TableLayout( + new ColumnLayout("Action", ColumnType.UInt16), + new ColumnLayout("Parent", ColumnType.HasDeclSecurity, GetColumnSize(ColumnType.HasDeclSecurity)), + new ColumnLayout("PermissionSet", ColumnType.Blob, BlobIndexSize)), + + // ClassLayout + new TableLayout( + new ColumnLayout("PackingSize", ColumnType.UInt16), + new ColumnLayout("ClassSize", ColumnType.UInt32), + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), + + // FieldLayout + new TableLayout( + new ColumnLayout("Offset", ColumnType.UInt32), + new ColumnLayout("Field", ColumnType.TypeDef, GetColumnSize(ColumnType.Field))), + + // StandAloneSig + new TableLayout( + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // EventMap + new TableLayout( + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("EventList", ColumnType.Event, GetColumnSize(ColumnType.Event))), + + // EventPtr + new TableLayout( + new ColumnLayout("Event", ColumnType.Event, GetColumnSize(ColumnType.Event))), + + // Event + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("EventType", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // PropertyMap + new TableLayout( + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("PropertyList", ColumnType.Event, GetColumnSize(ColumnType.Property))), + + // PropertyPtr + new TableLayout( + new ColumnLayout("Property", ColumnType.Property, GetColumnSize(ColumnType.Property))), + + // Property + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("PropertyType", ColumnType.Blob, BlobIndexSize)), + + // MethodSemantics + new TableLayout( + new ColumnLayout("Semantic", ColumnType.UInt16), + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("Association", ColumnType.HasSemantics, GetColumnSize(ColumnType.HasSemantics))), + + // MethodImpl + new TableLayout( + new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("MethodBody", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef)), + new ColumnLayout("MethodDeclaration", ColumnType.MethodDefOrRef, + GetColumnSize(ColumnType.MethodDefOrRef))), + + // ModuleRef + new TableLayout( + new ColumnLayout("Name", ColumnType.String, StringIndexSize)), + + // TypeSpec + new TableLayout( + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // ImplMap + new TableLayout( + new ColumnLayout("MappingFlags", ColumnType.UInt16), + new ColumnLayout("MemberForwarded", ColumnType.MemberForwarded, + GetColumnSize(ColumnType.MemberForwarded)), + new ColumnLayout("ImportName", ColumnType.String, StringIndexSize), + new ColumnLayout("ImportScope", ColumnType.ModuleRef, GetColumnSize(ColumnType.ModuleRef))), + + // FieldRva + new TableLayout( + new ColumnLayout("RVA", ColumnType.UInt32), + new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), + + // EncLog + new TableLayout( + new ColumnLayout("Token", ColumnType.UInt32), + new ColumnLayout("FuncCode", ColumnType.UInt32)), + + // EncMap + new TableLayout( + new ColumnLayout("Token", ColumnType.UInt32)), + + // Assembly + new TableLayout( + new ColumnLayout("HashAlgId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt16), + new ColumnLayout("MinorVersion", ColumnType.UInt16), + new ColumnLayout("BuildNumber", ColumnType.UInt16), + new ColumnLayout("RevisionNumber", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("PublicKey", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Culture", ColumnType.String, StringIndexSize)), + + // AssemblyProcessor + new TableLayout( + new ColumnLayout("Processor", ColumnType.UInt32)), + + // AssemblyOS + new TableLayout( + new ColumnLayout("PlatformId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt32), + new ColumnLayout("MinorVersion", ColumnType.UInt32)), + + // AssemblyRef + new TableLayout( + new ColumnLayout("MajorVersion", ColumnType.UInt16), + new ColumnLayout("MinorVersion", ColumnType.UInt16), + new ColumnLayout("BuildNumber", ColumnType.UInt16), + new ColumnLayout("RevisionNumber", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("PublicKeyOrToken", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Culture", ColumnType.String, StringIndexSize), + new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), + + // AssemblyRefProcessor + new TableLayout( + new ColumnLayout("Processor", ColumnType.UInt32), + new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), + + // AssemblyRefOS + new TableLayout( + new ColumnLayout("PlatformId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt32), + new ColumnLayout("MinorVersion", ColumnType.UInt32), + new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), + + // File + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), + + // ExportedType + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("TypeDefId", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), + new ColumnLayout("Implementation", ColumnType.Implementation, + GetColumnSize(ColumnType.Implementation))), + + // ManifestResource + new TableLayout( + new ColumnLayout("Offset", ColumnType.UInt32), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Implementation", ColumnType.Implementation, + GetColumnSize(ColumnType.Implementation))), + + // NestedClass + new TableLayout( + new ColumnLayout("NestedClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("EnclosingClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), + + // GenericParam + new TableLayout( + new ColumnLayout("Number", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Owner", ColumnType.TypeOrMethodDef, GetColumnSize(ColumnType.TypeOrMethodDef)), + new ColumnLayout("EnclosingClass", ColumnType.String, StringIndexSize)), + + // MethodSpec + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.MethodDefOrRef)), + new ColumnLayout("Instantiation", ColumnType.Blob, BlobIndexSize)), + + // GenericParamConstraint + new TableLayout( + new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)), + new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // Unused + default, + default, + default, + + // Document + new TableLayout( + new ColumnLayout("Name", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("HashAlgorithm", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("Hash", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Language", ColumnType.Guid, GuidIndexSize)), + + // MethodDebugInformation + new TableLayout( + new ColumnLayout("Document", ColumnType.Document, GetColumnSize(ColumnType.Document)), + new ColumnLayout("SequencePoints", ColumnType.Blob, BlobIndexSize)), + + // LocalScope + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("ImportScope", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), + new ColumnLayout("VariableList", ColumnType.LocalVariable, GetColumnSize(ColumnType.LocalVariable)), + new ColumnLayout("ConstantList", ColumnType.LocalConstant, GetColumnSize(ColumnType.LocalConstant)), + new ColumnLayout("StartOffset", ColumnType.UInt32), + new ColumnLayout("Length", ColumnType.UInt32)), + + // LocalVariable + new TableLayout( + new ColumnLayout("Attributes", ColumnType.UInt16), + new ColumnLayout("Index", ColumnType.UInt16), + new ColumnLayout("VariableList", ColumnType.String, StringIndexSize)), + + // LocalConstant + new TableLayout( + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // ImportScope + new TableLayout( + new ColumnLayout("Parent", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), + new ColumnLayout("Imports", ColumnType.Blob, BlobIndexSize)), + + // StateMachineMethod + new TableLayout( + new ColumnLayout("MoveNextMethod", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("KickoffMethod", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // CustomDebugInformation + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasCustomDebugInformation, + GetColumnSize(ColumnType.HasCustomDebugInformation)), + new ColumnLayout("Kind", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + }; + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index d8f673e46..8c6089ad1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -9,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Represents the metadata stream containing tables defining each member in a .NET assembly. /// - public class TablesStream : SegmentBase, IMetadataStream + public partial class TablesStream : SegmentBase, IMetadataStream { /// /// The default name of a table stream using the compressed format. @@ -32,7 +33,7 @@ public class TablesStream : SegmentBase, IMetadataStream public const string UncompressedStreamName = "#Schema"; private readonly Dictionary _indexEncoders; - private readonly LazyVariable> _tables; + private readonly LazyVariable> _tables; private readonly LazyVariable> _layouts; /// @@ -41,7 +42,7 @@ public class TablesStream : SegmentBase, IMetadataStream public TablesStream() { _layouts = new LazyVariable>(GetTableLayouts); - _tables = new LazyVariable>(GetTables); + _tables = new LazyVariable>(GetTables); _indexEncoders = CreateIndexEncoders(); } @@ -192,6 +193,29 @@ public uint ExtraData set; } + /// + /// Gets a value indicating whether the tables stream is assigned with row counts that originate from an + /// external .NET metadata file. + /// + /// + /// This value is typically set to false, except for Portable PDB metadata table streams. + /// + [MemberNotNullWhen(true, nameof(ExternalRowCounts))] + public bool HasExternalRowCounts => ExternalRowCounts is not null; + + /// + /// Gets or sets an array of row counts originating from an external .NET metadata file that this table stream + /// should consider when encoding indices. + /// + /// + /// This value is typically null, except for Portable PDB metadata table streams. + /// + public uint[]? ExternalRowCounts + { + get; + set; + } + /// /// Gets a collection of all tables in the tables stream. /// @@ -199,7 +223,7 @@ public uint ExtraData /// This collection always contains all tables, in the same order as defines, regardless /// of whether a table actually has elements or not. /// - protected IList Tables => _tables.Value; + protected IList Tables => _tables.Value; /// /// Gets the layout of all tables in the stream. @@ -209,6 +233,36 @@ public uint ExtraData /// public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException(); + /// + /// Obtains the implied table row count for the provided table index. + /// + /// The table index. + /// The row count. + /// + /// This method takes any external row counts from into account. + /// + public uint GetTableRowCount(TableIndex table) + { + return HasExternalRowCounts && (int) table < ExternalRowCounts.Length + ? ExternalRowCounts[(int) table] + : (uint) GetTable(table).Count; + } + + /// + /// Obtains the implied table index size for the provided table index. + /// + /// The table index. + /// The index size. + /// + /// This method takes any external row counts from into account. + /// + public IndexSize GetTableIndexSize(TableIndex table) + { + return GetTableRowCount(table) > 0xFFFF + ? IndexSize.Long + : IndexSize.Short; + } + /// /// Updates the layouts of each metadata table, according to the property. /// @@ -220,7 +274,7 @@ protected void SynchronizeTableLayoutsWithFlags() { var layouts = GetTableLayouts(); for (int i = 0; i < Tables.Count; i++) - Tables[i].UpdateTableLayout(layouts[i]); + Tables[i]?.UpdateTableLayout(layouts[i]); } /// @@ -270,11 +324,11 @@ public override void Write(IBinaryStreamWriter writer) protected virtual ulong ComputeValidBitmask() { // TODO: make more configurable (maybe add IMetadataTable.IsPresent property?). - ulong result = 0; + for (int i = 0; i < Tables.Count; i++) { - if (Tables[i].Count > 0) + if (Tables[i]?.Count > 0) result |= 1UL << i; } @@ -288,9 +342,40 @@ protected virtual ulong ComputeValidBitmask() /// The valid bitmask. protected virtual ulong ComputeSortedBitmask() { - // TODO: make more configurable (maybe add IMetadataTable.IsSorted property?). + ulong result = 0; + + bool containsTypeSystemData = false; + bool containsPdbData = false; + + // Determine which tables are marked as sorted. + for (int i = 0; i < Tables.Count; i++) + { + if (Tables[i] is not { } table) + continue; + + if (table.IsSorted) + result |= 1UL << i; - return 0x000016003301FA00; + if (table.Count > 0) + { + if (i <= (int) TableIndex.MaxTypeSystemTableIndex) + containsTypeSystemData = true; + else + containsPdbData = true; + } + } + + const ulong typeSystemMask = (1UL << (int) TableIndex.MaxTypeSystemTableIndex + 1) - 1; + const ulong pdbMask = ((1UL << (int) TableIndex.Max) - 1) & ~typeSystemMask; + + // Backwards compatibility: Ensure that only the bits are set in the sorted mask if the metadata + // actually contains the type system and/or pdb tables. + if (!containsTypeSystemData) + result &= ~typeSystemMask; + if (!containsPdbData) + result &= ~pdbMask; + + return result; } /// @@ -301,7 +386,7 @@ protected virtual ulong ComputeSortedBitmask() protected virtual int GetTablesCount(ulong validBitmask) { int count = 0; - for (TableIndex i = 0; i < (TableIndex) Tables.Count; i++) + for (TableIndex i = 0; i < TableIndex.Max; i++) { if (HasTable(validBitmask, i)) count++; @@ -337,7 +422,7 @@ protected virtual uint GetTablesSize(ulong validBitmask) /// The valid bitmask, indicating all present tables in the stream. protected virtual void WriteRowCounts(IBinaryStreamWriter writer, ulong validBitmask) { - for (TableIndex i = 0; i < (TableIndex) Tables.Count; i++) + for (TableIndex i = 0; i <= TableIndex.Max; i++) { if (HasTable(validBitmask, i)) writer.WriteInt32(GetTable(i).Count); @@ -380,111 +465,13 @@ protected static bool IsSorted(ulong sortedMask, TableIndex table) return ((sortedMask >> (int) table) & 1) != 0; } - /// - /// Obtains the collection of tables in the tables stream. - /// - /// The tables, including empty tables if there are any. - /// - /// This method is called upon initialization of the property. - /// - protected virtual IList GetTables() - { - var layouts = TableLayouts; - return new IMetadataTable[] - { - new MetadataTable(TableIndex.Module, layouts[0]), - new MetadataTable(TableIndex.TypeRef, layouts[1]), - new MetadataTable(TableIndex.TypeDef, layouts[2]), - new MetadataTable(TableIndex.FieldPtr, layouts[3]), - new MetadataTable(TableIndex.Field, layouts[4]), - new MetadataTable(TableIndex.Method, layouts[5]), - new MetadataTable(TableIndex.Method, layouts[6]), - new MetadataTable(TableIndex.ParamPtr, layouts[7]), - new MetadataTable(TableIndex.Param, layouts[8]), - new MetadataTable(TableIndex.InterfaceImpl, layouts[9]), - new MetadataTable(TableIndex.MemberRef, layouts[10]), - new MetadataTable(TableIndex.Constant, layouts[11]), - new MetadataTable(TableIndex.CustomAttribute, layouts[12]), - new MetadataTable(TableIndex.FieldMarshal, layouts[13]), - new MetadataTable(TableIndex.DeclSecurity, layouts[14]), - new MetadataTable(TableIndex.ClassLayout, layouts[15]), - new MetadataTable(TableIndex.FieldLayout, layouts[16]), - new MetadataTable(TableIndex.StandAloneSig, layouts[17]), - new MetadataTable(TableIndex.EventMap, layouts[18]), - new MetadataTable(TableIndex.EventPtr, layouts[19]), - new MetadataTable(TableIndex.Event, layouts[20]), - new MetadataTable(TableIndex.PropertyMap, layouts[21]), - new MetadataTable(TableIndex.PropertyPtr, layouts[22]), - new MetadataTable(TableIndex.Property, layouts[23]), - new MetadataTable(TableIndex.MethodSemantics, layouts[24]), - new MetadataTable(TableIndex.MethodImpl, layouts[25]), - new MetadataTable(TableIndex.ModuleRef, layouts[26]), - new MetadataTable(TableIndex.TypeSpec, layouts[27]), - new MetadataTable(TableIndex.ImplMap, layouts[28]), - new MetadataTable(TableIndex.FieldRva, layouts[29]), - new MetadataTable(TableIndex.EncLog, layouts[30]), - new MetadataTable(TableIndex.EncMap, layouts[31]), - new MetadataTable(TableIndex.Assembly, layouts[32]), - new MetadataTable(TableIndex.AssemblyProcessor, layouts[33]), - new MetadataTable(TableIndex.AssemblyOS, layouts[34]), - new MetadataTable(TableIndex.AssemblyRef, layouts[35]), - new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[36]), - new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[37]), - new MetadataTable(TableIndex.File, layouts[38]), - new MetadataTable(TableIndex.ExportedType, layouts[39]), - new MetadataTable(TableIndex.ManifestResource, layouts[40]), - new MetadataTable(TableIndex.NestedClass, layouts[41]), - new MetadataTable(TableIndex.GenericParam, layouts[42]), - new MetadataTable(TableIndex.MethodSpec, layouts[43]), - new MetadataTable(TableIndex.GenericParamConstraint, layouts[44]), - }; - } - - private Dictionary CreateIndexEncoders() - { - return new() - { - [CodedIndex.TypeDefOrRef] = new IndexEncoder(this, - TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec), - [CodedIndex.HasConstant] = new(this, - TableIndex.Field, TableIndex.Param, TableIndex.Property), - [CodedIndex.HasCustomAttribute] = new(this, - TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, - TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, - TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, - TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, - TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, - TableIndex.GenericParamConstraint, TableIndex.MethodSpec), - [CodedIndex.HasFieldMarshal] = new(this, - TableIndex.Field, TableIndex.Param), - [CodedIndex.HasDeclSecurity] = new(this, - TableIndex.TypeDef, TableIndex.Method, TableIndex.Assembly), - [CodedIndex.MemberRefParent] = new(this, - TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef, - TableIndex.Method, TableIndex.TypeSpec), - [CodedIndex.HasSemantics] = new(this, - TableIndex.Event, TableIndex.Property), - [CodedIndex.MethodDefOrRef] = new(this, - TableIndex.Method, TableIndex.MemberRef), - [CodedIndex.MemberForwarded] = new(this, - TableIndex.Field, TableIndex.Method), - [CodedIndex.Implementation] = new(this, - TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType), - [CodedIndex.CustomAttributeType] = new(this, - 0, 0, TableIndex.Method, TableIndex.MemberRef, 0), - [CodedIndex.ResolutionScope] = new(this, - TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef), - [CodedIndex.TypeOrMethodDef] = new(this, - TableIndex.TypeDef, TableIndex.Method) - }; - } - /// /// Gets a table by its table index. /// /// The table index. /// The table. - public virtual IMetadataTable GetTable(TableIndex index) => Tables[(int) index]; + public virtual IMetadataTable GetTable(TableIndex index) => + Tables[(int) index] ?? throw new ArgumentOutOfRangeException(nameof(index)); /// /// Gets a table by its row type. @@ -506,7 +493,7 @@ public virtual MetadataTable GetTable() public virtual MetadataTable GetTable(TableIndex index) where TRow : struct, IMetadataRow { - return (MetadataTable) Tables[(int) index]; + return (MetadataTable) (Tables[(int) index] ?? throw new ArgumentOutOfRangeException(nameof(index))); } private IndexSize GetStreamIndexSize(int bitIndex) => (IndexSize) (((((int) Flags >> bitIndex) & 1) + 1) * 2); @@ -526,29 +513,25 @@ protected virtual uint GetColumnSize(ColumnType columnType) { if (_layouts.IsInitialized) { - if (columnType <= ColumnType.GenericParamConstraint) - return (uint) Tables[(int) columnType].IndexSize; - if (columnType <= ColumnType.TypeOrMethodDef) - return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize; + switch (columnType) + { + case <= ColumnType.CustomDebugInformation: + return (uint) GetTableIndexSize((TableIndex) columnType); + case <= ColumnType.HasCustomDebugInformation: + return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize; + } } - switch (columnType) + return columnType switch { - case ColumnType.Blob: - return (uint) BlobIndexSize; - case ColumnType.String: - return (uint) StringIndexSize; - case ColumnType.Guid: - return (uint) GuidIndexSize; - case ColumnType.Byte: - return sizeof(byte); - case ColumnType.UInt16: - return sizeof(ushort); - case ColumnType.UInt32: - return sizeof(uint); - default: - return sizeof(uint); - } + ColumnType.Blob => (uint) BlobIndexSize, + ColumnType.String => (uint) StringIndexSize, + ColumnType.Guid => (uint) GuidIndexSize, + ColumnType.Byte => sizeof(byte), + ColumnType.UInt16 => sizeof(ushort), + ColumnType.UInt32 => sizeof(uint), + _ => sizeof(uint) + }; } /// @@ -558,197 +541,6 @@ protected virtual uint GetColumnSize(ColumnType columnType) /// The encoder. public IndexEncoder GetIndexEncoder(CodedIndex index) => _indexEncoders[index]; - /// - /// Gets an ordered collection of the current table layouts. - /// - /// The table layouts. - protected TableLayout[] GetTableLayouts() - { - var result = new[] - { - new TableLayout( - new ColumnLayout("Generation", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Mvid", ColumnType.Guid, GuidIndexSize), - new ColumnLayout("EncId", ColumnType.Guid, GuidIndexSize), - new ColumnLayout("EncBaseId", ColumnType.Guid, GuidIndexSize)), - new TableLayout( - new ColumnLayout("ResolutionScope", ColumnType.ResolutionScope, - GetColumnSize(ColumnType.ResolutionScope)), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.Guid, StringIndexSize)), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), - new ColumnLayout("Extends", ColumnType.TypeDefOrRef, - GetColumnSize(ColumnType.TypeDefOrRef)), - new ColumnLayout("FieldList", ColumnType.Field, GetColumnSize(ColumnType.Field)), - new ColumnLayout("MethodList", ColumnType.Method, GetColumnSize(ColumnType.Method))), - new TableLayout( - new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method))), - new TableLayout( - new ColumnLayout("RVA", ColumnType.UInt32), - new ColumnLayout("ImplFlags", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("ParamList", ColumnType.Param, GetColumnSize(ColumnType.Param))), - new TableLayout( - new ColumnLayout("Parameter", ColumnType.Param, GetColumnSize(ColumnType.Param))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Sequence", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("Interface", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - new TableLayout( - new ColumnLayout("Parent", ColumnType.MemberRefParent, GetColumnSize(ColumnType.MemberRefParent)), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Type", ColumnType.Byte), - new ColumnLayout("Padding", ColumnType.Byte), - new ColumnLayout("Parent", ColumnType.HasConstant, GetColumnSize(ColumnType.HasConstant)), - new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.HasCustomAttribute, GetColumnSize(ColumnType.HasCustomAttribute)), - new ColumnLayout("Type", ColumnType.CustomAttributeType, GetColumnSize(ColumnType.CustomAttributeType)), - new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.HasFieldMarshal, GetColumnSize(ColumnType.HasFieldMarshal)), - new ColumnLayout("NativeType", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Action", ColumnType.UInt16), - new ColumnLayout("Parent", ColumnType.HasDeclSecurity, GetColumnSize(ColumnType.HasDeclSecurity)), - new ColumnLayout("PermissionSet", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("PackingSize", ColumnType.UInt16), - new ColumnLayout("ClassSize", ColumnType.UInt32), - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), - new TableLayout( - new ColumnLayout("Offset", ColumnType.UInt32), - new ColumnLayout("Field", ColumnType.TypeDef, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("EventList", ColumnType.Event, GetColumnSize(ColumnType.Event))), - new TableLayout( - new ColumnLayout("Event", ColumnType.Event, GetColumnSize(ColumnType.Event))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("EventType", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - new TableLayout( - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("PropertyList", ColumnType.Event, GetColumnSize(ColumnType.Property))), - new TableLayout( - new ColumnLayout("Property", ColumnType.Property, GetColumnSize(ColumnType.Property))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("PropertyType", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Semantic", ColumnType.UInt16), - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), - new ColumnLayout("Association", ColumnType.HasSemantics, GetColumnSize(ColumnType.HasSemantics))), - new TableLayout( - new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("MethodBody", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef)), - new ColumnLayout("MethodDeclaration", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef))), - new TableLayout( - new ColumnLayout("Name", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("MappingFlags", ColumnType.UInt16), - new ColumnLayout("MemberForwarded", ColumnType.MemberForwarded, GetColumnSize(ColumnType.MemberForwarded)), - new ColumnLayout("ImportName", ColumnType.String, StringIndexSize), - new ColumnLayout("ImportScope", ColumnType.ModuleRef, GetColumnSize(ColumnType.ModuleRef))), - new TableLayout( - new ColumnLayout("RVA", ColumnType.UInt32), - new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Token", ColumnType.UInt32), - new ColumnLayout("FuncCode", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("Token", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("HashAlgId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt16), - new ColumnLayout("MinorVersion", ColumnType.UInt16), - new ColumnLayout("BuildNumber", ColumnType.UInt16), - new ColumnLayout("RevisionNumber", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("PublicKey", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Culture", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Processor", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("PlatformId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt32), - new ColumnLayout("MinorVersion", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("MajorVersion", ColumnType.UInt16), - new ColumnLayout("MinorVersion", ColumnType.UInt16), - new ColumnLayout("BuildNumber", ColumnType.UInt16), - new ColumnLayout("RevisionNumber", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("PublicKeyOrToken", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Culture", ColumnType.String, StringIndexSize), - new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Processor", ColumnType.UInt32), - new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), - new TableLayout( - new ColumnLayout("PlatformId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt32), - new ColumnLayout("MinorVersion", ColumnType.UInt32), - new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("TypeDefId", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), - new ColumnLayout("Implementation", ColumnType.Implementation, GetColumnSize(ColumnType.Implementation))), - new TableLayout( - new ColumnLayout("Offset", ColumnType.UInt32), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Implementation", ColumnType.Implementation, GetColumnSize(ColumnType.Implementation))), - new TableLayout( - new ColumnLayout("NestedClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("EnclosingClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), - new TableLayout( - new ColumnLayout("Number", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Owner", ColumnType.TypeOrMethodDef, GetColumnSize(ColumnType.TypeOrMethodDef)), - new ColumnLayout("EnclosingClass", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.MethodDefOrRef)), - new ColumnLayout("Instantiation", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)), - new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - }; - - return result; - } - /// /// Gets the range of metadata tokens referencing fields that a type defines. /// diff --git a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs index c75dd2a25..6ab56fa40 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 DotNet.Metadata.Metadata.FromReader(directoryReader, MetadataReaderContext.FromReaderContext(_context)); } /// @@ -126,7 +125,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader } var vtables = new VTableFixupsDirectory(); - vtables.UpdateOffsets(directoryReader.Offset, directoryReader.Rva); + vtables.UpdateOffsets(_context.GetRelocation(directoryReader.Offset, directoryReader.Rva)); for (int i = 0; i < directoryReader.Length / 8; i++) { diff --git a/src/AsmResolver.PE/DotNet/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 X64RuntimeFunction? ChainedFunction public static X64UnwindInfo FromReader(PEReaderContext context, ref BinaryStreamReader reader) { var result = new X64UnwindInfo(); - result.UpdateOffsets(reader.Offset, reader.Rva); + result.UpdateOffsets(context.GetRelocation(reader.Offset, reader.Rva)); result._firstByte = reader.ReadByte(); result.SizeOfProlog = reader.ReadByte(); diff --git a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs index 8bf5526fd..8409daf39 100644 --- a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs @@ -83,10 +83,10 @@ public void AddDirectory(IExportDirectory exportDirectory) } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - _contentsBuilder.UpdateOffsets(newOffset + ExportDirectoryHeaderSize, newRva + ExportDirectoryHeaderSize); + base.UpdateOffsets(parameters); + _contentsBuilder.UpdateOffsets(parameters.WithAdvance(ExportDirectoryHeaderSize)); } /// diff --git a/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs b/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs index f2a94395f..62275e716 100644 --- a/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs @@ -15,23 +15,23 @@ public class HintNameTableBuffer : SegmentBase private uint _length; /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - ulong currentOffset = newOffset; + ulong currentOffset = parameters.Offset; foreach (var module in _modules) { foreach (var entry in module.Symbols) { if (entry.IsImportByName) { - _hintNameOffsets[entry] = (uint) (currentOffset - newOffset); + _hintNameOffsets[entry] = (uint) (currentOffset - parameters.Offset); currentOffset += (uint) (sizeof(ushort) + Encoding.ASCII.GetByteCount(entry.Name) + 1); currentOffset = currentOffset.Align(2); } } - _moduleNameOffsets[module] = (uint) (currentOffset - newOffset); + _moduleNameOffsets[module] = (uint) (currentOffset - parameters.Offset); if (module.Name is not null) currentOffset += (uint) Encoding.ASCII.GetByteCount(module.Name); @@ -39,7 +39,7 @@ public override void UpdateOffsets(ulong newOffset, uint newRva) currentOffset++; } - _length = (uint) (currentOffset - newOffset); + _length = (uint) (currentOffset - parameters.Offset); } /// diff --git a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs index 7bcc57074..8b24c41d0 100644 --- a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs @@ -17,17 +17,19 @@ public ImportAddressDirectoryBuffer(HintNameTableBuffer hintNameTable, bool is32 } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - foreach (var module in Modules) + var current = parameters; + for (int i = 0; i < Modules.Count; i++) { + var module = Modules[i]; + var thunkTable = GetModuleThunkTable(module); uint size = thunkTable.GetPhysicalSize(); - thunkTable.UpdateOffsets(newOffset, newRva); - newOffset += size; - newRva += size; + thunkTable.UpdateOffsets(current); + current.Advance(size); } } diff --git a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs index bda2b1f17..5bf7adb61 100644 --- a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs @@ -42,23 +42,22 @@ public override void AddModule(IImportedModule module) } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - newOffset += _entriesLength; - newRva += _entriesLength; + var current = parameters.WithAdvance(_entriesLength); foreach (var module in Modules) { var thunkTable = GetModuleThunkTable(module); uint size = thunkTable.GetPhysicalSize(); - thunkTable.UpdateOffsets(newOffset, newRva); - newOffset += size; - newRva += size; + thunkTable.UpdateOffsets(current); + + current.Advance(size); } - HintNameTable.UpdateOffsets(newOffset, newRva); + HintNameTable.UpdateOffsets(current); } /// diff --git a/src/AsmResolver.PE/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/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 @@ public abstract bool IsClrBootstrapperRequired /// /// 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. diff --git a/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs b/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs index 71d31c474..92aa70a2e 100644 --- a/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs @@ -58,9 +58,9 @@ private static RelocationBlock GetOrCreateBlock(IDictionary - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); _blocks = null; } diff --git a/src/AsmResolver.PE/Tls/ITlsDirectory.cs b/src/AsmResolver.PE/Tls/ITlsDirectory.cs index b07ec8000..9149c0314 100644 --- a/src/AsmResolver.PE/Tls/ITlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/ITlsDirectory.cs @@ -54,24 +54,6 @@ TlsCharacteristics Characteristics set; } - /// - /// Gets or sets the image base address that the TLS directory assumes. - /// - public ulong ImageBase - { - get; - set; - } - - /// - /// Gets or sets a value indicating whether the TLS directory assumes a 32-bit or 64-bit format. - /// - public bool Is32Bit - { - get; - set; - } - /// /// Obtains a collection of base address relocations that need to be applied to the TLS data directory /// after the image was loaded into memory. diff --git a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs index 99c32faa1..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 ddeb3f368..dc29af2aa 100644 --- a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs +++ b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs @@ -9,6 +9,8 @@ namespace AsmResolver.PE.Tls public class TlsCallbackCollection : Collection, ISegment { private readonly ITlsDirectory _owner; + private ulong _imageBase = 0x00400000; + private bool _is32Bit = true; internal TlsCallbackCollection(ITlsDirectory owner) { @@ -33,16 +35,18 @@ public uint Rva public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; + _imageBase = parameters.ImageBase; + _is32Bit = parameters.Is32Bit; } /// public uint GetPhysicalSize() { - uint pointerSize = (uint) (_owner.Is32Bit ? sizeof(uint) : sizeof(ulong)); + uint pointerSize = (uint) (_is32Bit ? sizeof(uint) : sizeof(ulong)); return (uint) (pointerSize * (Count + 1)); } @@ -52,8 +56,8 @@ public uint GetPhysicalSize() /// public void Write(IBinaryStreamWriter writer) { - ulong imageBase = _owner.ImageBase; - bool is32Bit = _owner.Is32Bit; + ulong imageBase = _imageBase; + bool is32Bit = _is32Bit; for (int i = 0; i < Items.Count; i++) writer.WriteNativeInt(imageBase + Items[i].Rva, is32Bit); diff --git a/src/AsmResolver.PE/Tls/TlsDirectory.cs b/src/AsmResolver.PE/Tls/TlsDirectory.cs index fbe6c101c..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/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs b/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs index 987dc8412..ea7bc9067 100644 --- a/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs @@ -104,7 +104,7 @@ private void AddDataEntry(IResourceEntry entry) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _segments.GetPhysicalSize(); diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj index b0b3df765..091cf3c05 100644 --- a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj +++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj @@ -21,7 +21,15 @@ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs new file mode 100644 index 000000000..32e5bccc7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf containing a list of type arguments for a function or method. +/// +public class ArgumentListLeaf : CodeViewLeaf +{ + private IList? _types; + + /// + /// Initializes an empty argument list. + /// + /// The type index to assign to the list. + protected ArgumentListLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty argument list. + /// + public ArgumentListLeaf() + : base(0) + { + } + + /// + /// Creates a new argument list. + /// + public ArgumentListLeaf(params CodeViewTypeRecord[] argumentTypes) + : base(0) + { + _types = new List(argumentTypes); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.ArgList; + + /// + /// Gets an ordered collection of types that correspond to the types of each parameter. + /// + public IList Types + { + get + { + if (_types is null) + Interlocked.CompareExchange(ref _types, GetArgumentTypes(), null); + return _types; + } + } + + /// + /// Obtains the argument types stored in the list. + /// + /// The types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetArgumentTypes() => new List(); + + /// + public override string ToString() => string.Join(", ", Types); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs new file mode 100644 index 000000000..1f53f2071 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs @@ -0,0 +1,123 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type describing an array of elements. +/// +public class ArrayTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _elementType; + private readonly LazyVariable _indexType; + private readonly LazyVariable _name; + + /// + /// Initializes a new empty array type. + /// + /// The type index to assign to the type. + protected ArrayTypeRecord(uint typeIndex) + : base(typeIndex) + { + _elementType = new LazyVariable(GetElementType); + _indexType = new LazyVariable(GetIndexType); + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new array type. + /// + /// The type of each element in the array. + /// The type to use for indexing into the array. + /// The number of elements in the array. + public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length) + : base(0) + { + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); + Length = length; + _name = new LazyVariable(Utf8String.Empty); + } + + /// + /// Creates a new array type. + /// + /// The type of each element in the array. + /// The type to use for indexing into the array. + /// The number of elements in the array. + /// The name of the array type. + public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length, Utf8String name) + : base(0) + { + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); + Length = length; + _name = new LazyVariable(Name); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Array; + + /// + /// Gets or sets the type of each element in the array. + /// + public CodeViewTypeRecord? ElementType + { + get => _elementType.Value; + set => _elementType.Value = value; + } + + /// + /// Gets or sets the type that is used to index into the array. + /// + public CodeViewTypeRecord? IndexType + { + get => _indexType.Value; + set => _indexType.Value = value; + } + + /// + /// Gets or sets the number of elements in the array. + /// + public ulong Length + { + get; + set; + } + + /// + /// Gets or sets the name of the type. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the element type of the array. + /// + /// The element type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetElementType() => null; + + /// + /// Obtains the index type of the array. + /// + /// The index type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetIndexType() => null; + + /// + /// Obtains the name type of the array. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => $"{ElementType}[{Length}]"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs new file mode 100644 index 000000000..3a3823945 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs @@ -0,0 +1,62 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a reference to a base class object in a structure. +/// +public class BaseClassField : CodeViewField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty base class. + /// + /// The type index to assign to the base class field. + protected BaseClassField(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new base class field. + /// + /// The base type to reference. + public BaseClassField(CodeViewTypeRecord type) + : base(0) + { + _type = new LazyVariable(type); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BClass; + + /// + /// Gets or sets the base type that this base class is referencing. + /// + public CodeViewTypeRecord? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the offset of the base within the class. + /// + public ulong Offset + { + get; + set; + } + + /// + /// Obtains the base type that the class is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => Type?.ToString() ?? "<<>>"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs new file mode 100644 index 000000000..40a01fbb5 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs @@ -0,0 +1,75 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a bit field type. +/// +public class BitFieldTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty bit field record. + /// + /// The type index to assign to the bit field type. + protected BitFieldTypeRecord(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new bit field record. + /// + /// The type of the bit field. + /// The bit index the bit field starts at. + /// The number of bits the bit field spans. + public BitFieldTypeRecord(CodeViewTypeRecord type, byte position, byte length) + : base(0) + { + _type = new LazyVariable(type); + Position = position; + Length = length; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BitField; + + /// + /// Gets or sets the base type that this bit field is referencing. + /// + public CodeViewTypeRecord? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the bit index that this bit fields starts at. + /// + public byte Position + { + get; + set; + } + + /// + /// Gets or sets the number of bits that this bit fields spans. + /// + public byte Length + { + get; + set; + } + + /// + /// Obtains the base type that the bit field is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => $"{Type} : {Length}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs new file mode 100644 index 000000000..3f222effc --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs @@ -0,0 +1,110 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a class, structure or interface type in a PDB. +/// +public class ClassTypeRecord : CodeViewDerivedTypeRecord +{ + private readonly LazyVariable _uniqueName; + private readonly LazyVariable _vtableShape; + + /// + /// Initializes an empty class type. + /// + /// The kind of type. + /// The type index to assign to the class type. + /// + /// Occurs when the provided kind is not a class, structure or interface. + /// + protected ClassTypeRecord(CodeViewLeafKind kind, uint typeIndex) + : base(typeIndex) + { + if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface)) + throw new ArgumentOutOfRangeException(nameof(kind)); + + LeafKind = kind; + _uniqueName = new LazyVariable(GetUniqueName); + _vtableShape = new LazyVariable(GetVTableShape); + } + + /// + /// Creates a new class type record. + /// + /// The kind. + /// The name of the type. + /// The unique mangled name of the type. + /// The size in bytes of the type. + /// Attributes describing the shape of the type. + /// The type that this type is derived from, if any. + /// + /// Occurs when the provided kind is not a class, structure or interface. + /// + public ClassTypeRecord(CodeViewLeafKind kind, Utf8String name, Utf8String uniqueName, ulong size, + StructureAttributes attributes, CodeViewTypeRecord? baseType) + : base(0) + { + if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface)) + throw new ArgumentOutOfRangeException(nameof(kind)); + + LeafKind = kind; + Name = name; + _uniqueName = new LazyVariable(uniqueName); + _vtableShape = new LazyVariable(default(VTableShapeLeaf)); + Size = size; + StructureAttributes = attributes; + BaseType = baseType; + } + + /// + public override CodeViewLeafKind LeafKind + { + get; + } + + /// + /// Gets or sets the number bytes that this class spans. + /// + public ulong Size + { + get; + set; + } + + /// + /// Gets or sets the uniquely identifiable name for this type. + /// + public Utf8String UniqueName + { + get => _uniqueName.Value; + set => _uniqueName.Value = value; + } + + /// + /// Gets or sets the shape of the virtual function table of this type, if available. + /// + public VTableShapeLeaf? VTableShape + { + get => _vtableShape.Value; + set => _vtableShape.Value = value; + } + + /// + /// Obtains the uniquely identifiable name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetUniqueName() => Utf8String.Empty; + + /// + /// Obtains the shape of the virtual function table name of the type. + /// + /// The shape. + /// + /// This method is called upon initialization of the property. + /// + protected virtual VTableShapeLeaf? GetVTableShape() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs new file mode 100644 index 000000000..e17d8f881 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs @@ -0,0 +1,132 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all calling conventions that can be specified in a PDB image. +/// +public enum CodeViewCallingConvention : byte +{ + /// + /// Indicates a near call using the cdecl calling convention. + /// + NearC = 0x00, + + /// + /// Indicates a far call using the cdecl calling convention. + /// + FarC = 0x01, + + /// + /// Indicates a near call using the Pascal calling convention. + /// + NearPascal = 0x02, + + /// + /// Indicates a far call using the Pascal calling convention. + /// + FarPascal = 0x03, + + /// + /// Indicates a near call using the fastcall calling convention. + /// + NearFast = 0x04, + + /// + /// Indicates a far call using the fastcall calling convention. + /// + FarFast = 0x05, + + /// + /// Skipped (unused) call index + /// + Skipped = 0x06, + + /// + /// Indicates a near call using the stdcall calling convention. + /// + NearStd = 0x07, + + /// + /// Indicates a far call using the stdcall calling convention. + /// + FarStd = 0x08, + + /// + /// Indicates a near call using the syscall calling convention. + /// + NearSys = 0x09, + + /// + /// Indicates a far call using the syscall calling convention. + /// + FarSys = 0x0a, + + /// + /// Indicates a call using the thiscall calling convention. + /// + ThisCall = 0x0b, + + /// + /// Indicates a call using the MIPS calling convention. + /// + MipsCall = 0x0c, + + /// + /// Indicates a generic calling sequence. + /// + Generic = 0x0d, + + /// + /// Indicates a call using the Alpha calling convention. + /// + AlphaCall = 0x0e, + + /// + /// Indicates a call using the PowerPC calling convention. + /// + PpcCall = 0x0f, + + /// + /// Indicates a call using the Hitachi SuperH calling convention. + /// + ShCall = 0x10, + + /// + /// Indicates a call using the ARM calling convention. + /// + ArmCall = 0x11, + + /// + /// Indicates a call using the AM33 calling convention. + /// + Am33Call = 0x12, + + /// + /// Indicates a call using the TriCore calling convention. + /// + TriCall = 0x13, + + /// + /// Indicates a call using the Hitachi SuperH-5 calling convention. + /// + Sh5Call = 0x14, + + /// + /// Indicates a call using the M32R calling convention. + /// + M32RCall = 0x15, + + /// + /// Indicates a call using the clr calling convention. + /// + ClrCall = 0x16, + + /// + /// Marker for routines always inlined and thus lacking a convention. + /// + Inline = 0x17, + + /// + /// Indicates a near call using the vectorcall calling convention. + /// + NearVector = 0x18, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs new file mode 100644 index 000000000..9abc46710 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs @@ -0,0 +1,66 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides a base for all code view types that can define one or more fields. +/// +public abstract class CodeViewCompositeTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _name; + private readonly LazyVariable _fields; + + /// + /// Initializes a new empty composite type. + /// + /// The type index to assign to the type. + protected CodeViewCompositeTypeRecord(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + _fields = new LazyVariable(GetFields); + } + + /// + /// Gets or sets the structural attributes assigned to the type. + /// + public StructureAttributes StructureAttributes + { + get; + set; + } + + /// + /// Gets or sets the name of the type. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Gets a collection of fields that are defined in the enum. + /// + public FieldListLeaf? Fields + { + get => _fields.Value; + set => _fields.Value = value; + } + + /// + /// Obtains the new name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the fields defined in the type. + /// + /// The fields. + /// + /// This method is called upon initialization of the property. + /// + protected virtual FieldListLeaf? GetFields() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs new file mode 100644 index 000000000..3821a14fe --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs @@ -0,0 +1,52 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a data member in a class or structure type. +/// +public abstract class CodeViewDataField : CodeViewNamedField +{ + private readonly LazyVariable _dataType; + + /// + /// Initializes an empty instance data member. + /// + /// The type index to assign to the member. + protected CodeViewDataField(uint typeIndex) + : base(typeIndex) + { + _dataType = new LazyVariable(GetDataType); + } + + /// + /// Creates a new data member. + /// + /// The data type of the member. + /// The name of the member. + protected CodeViewDataField(CodeViewTypeRecord dataType, Utf8String name) + : base(0) + { + _dataType = new LazyVariable(dataType); + Name = name; + } + + /// + /// Gets or sets the data type of the member. + /// + public CodeViewTypeRecord DataType + { + get => _dataType.Value; + set => _dataType.Value = value; + } + + /// + /// Obtains the data type of the member. + /// + /// The data type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetDataType() => null; + + /// + public override string ToString() => $"{DataType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs new file mode 100644 index 000000000..3cd51d0f8 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs @@ -0,0 +1,37 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides a base for all custom types that may be derived from a base type. +/// +public abstract class CodeViewDerivedTypeRecord : CodeViewCompositeTypeRecord +{ + private readonly LazyVariable _baseType; + + /// + protected CodeViewDerivedTypeRecord(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Gets or sets the base type that this type is deriving from. + /// + public CodeViewTypeRecord? BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Obtains the type that the type is derived from. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => Name; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs new file mode 100644 index 000000000..689105e37 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -0,0 +1,32 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single record in a field list of a TPI or IPI stream. +/// +public abstract class CodeViewField : CodeViewLeaf +{ + /// + /// Initializes an empty CodeView field leaf. + /// + /// The type index to assign to the leaf. + protected CodeViewField(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Gets or sets the attributes associated to the field. + /// + public CodeViewFieldAttributes Attributes + { + get; + set; + } + + /// + /// Gets a value indicating whether the field is a newly introduced virtual function. + /// + public bool IsIntroducingVirtual => + (Attributes & CodeViewFieldAttributes.IntroducingVirtual) == CodeViewFieldAttributes.IntroducingVirtual + || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) == CodeViewFieldAttributes.PureIntroducingVirtual; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs new file mode 100644 index 000000000..efbea90ec --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs @@ -0,0 +1,95 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all flags that can be assigned to a field, method or class. +/// +[Flags] +public enum CodeViewFieldAttributes : ushort +{ + /// + /// Indicates no attributes were assigned to the field. + /// + None = 0b00000000_00000000, + + /// + /// Indicates the field is marked private. + /// + Private = 0b00000000_00000001, + + /// + /// Indicates the field is marked protected. + /// + Protected = 0b00000000_00000010, + + /// + /// Indicates the field is marked public. + /// + Public = 0b00000000_00000011, + + /// + /// Provides the bit-mask that can be used to extract the access-level of the field. + /// + AccessMask = 0b00000000_00000011, + + /// + /// Indicates the method is a virtual method. + /// + Virtual = 0b00000000_00000100, + + /// + /// Indicates the method is a static method. + /// + Static = 0b00000000_00001000, + + /// + /// Indicates the method can be accessed only from within the current module. + /// + Friend = 0b00000000_00001100, + + /// + /// Indicates the method is a new introducing virtual method. + /// + IntroducingVirtual = 0b00000000_00010000, + + /// + /// Indicates the method is a pure virtual method. + /// + PureVirtual = 0b00000000_00010100, + + /// + /// Indicates the method is a new introducing pure virtual method. + /// + PureIntroducingVirtual = 0b00000000_00011000, + + /// + /// Provides the bit-mask that can be used to extract the method properties of the field. + /// + MethodPropertiesMask = 0b00000000_00011100, + + /// + /// Indicates the field is compiler generated and does not exist. + /// + Pseudo = 0b00000000_00100000, + + /// + /// Indicates the class cannot be inherited. + /// + NoInherit = 0b00000000_01000000, + + /// + /// Indicates the class cannot be constructed. + /// + NoConstruct = 0b00000000_10000000, + + /// + /// Indicates the field is compiler generated but does exist. + /// + CompilerGenerated = 0b00000001_00000000, + + /// + /// Indicates the method cannot be overridden. + /// + Sealed = 0b00000010_00000000 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs new file mode 100644 index 000000000..c966aa91e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -0,0 +1,99 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves.Serialized; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewLeafKind; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single leaf record in a TPI or IPI stream. +/// +public abstract class CodeViewLeaf +{ + /// + /// Initializes an empty CodeView leaf. + /// + /// The type index to assign to the leaf. + protected CodeViewLeaf(uint typeIndex) + { + TypeIndex = typeIndex; + } + + /// + /// Gets the type kind this record encodes. + /// + public abstract CodeViewLeafKind LeafKind + { + get; + } + + /// + /// Gets the type index the type is associated to. + /// + public uint TypeIndex + { + get; + internal set; + } + + internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + { + ushort length = reader.ReadUInt16(); + var dataReader = reader.Fork(); + reader.Offset += length; + + return FromReaderNoHeader(context, typeIndex, ref dataReader); + } + + internal static CodeViewLeaf FromReaderNoHeader( + PdbReaderContext context, + uint typeIndex, + ref BinaryStreamReader dataReader) + { + var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); + return kind switch + { + Array => new SerializedArrayTypeRecord(context, typeIndex, dataReader), + ArgList => new SerializedArgumentListLeaf(context, typeIndex, dataReader), + BClass => new SerializedBaseClassField(context, typeIndex, ref dataReader), + Class or Interface or Structure => new SerializedClassTypeRecord(kind, context, typeIndex, dataReader), + BitField => new SerializedBitFieldTypeRecord(context, typeIndex, dataReader), + Enum => new SerializedEnumTypeRecord(context, typeIndex, dataReader), + Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), + FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader), + Member => new SerializedInstanceDataField(context, typeIndex, ref dataReader), + Method => new SerializedOverloadedMethod(context, typeIndex, ref dataReader), + MethodList => new SerializedMethodListLeaf(context, typeIndex, dataReader), + MFunction => new SerializedMemberFunctionLeaf(context, typeIndex, dataReader), + Modifier => new SerializedModifierTypeRecord(context, typeIndex, dataReader), + NestType or NestTypeEx => new SerializedNestedTypeField(context, typeIndex, ref dataReader), + OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader), + Pointer => new SerializedPointerTypeRecord(context, typeIndex, dataReader), + Procedure => new SerializedProcedureTypeRecord(context, typeIndex, dataReader), + StMember => new SerializedStaticDataField(context, typeIndex, ref dataReader), + Union => new SerializedUnionTypeRecord(context, typeIndex, dataReader), + VFuncTab => new SerializedVTableField(context, typeIndex, ref dataReader), + VTShape => new SerializedVTableShapeLeaf(context, typeIndex, dataReader), + VBClass or IVBClass => new SerializedVBaseClassField(context, typeIndex, ref dataReader, kind == IVBClass), + _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) + }; + } + + internal static object ReadNumeric(ref BinaryStreamReader reader) + { + var kind = (CodeViewLeafKind) reader.ReadUInt16(); + return kind switch + { + < Numeric => (object) (uint) kind, + Char => (char) reader.ReadByte(), + Short => reader.ReadInt16(), + UShort => reader.ReadUInt16(), + Long => reader.ReadInt32(), + ULong => reader.ReadUInt32(), + QuadWord => reader.ReadInt64(), + UQuadWord => reader.ReadUInt64(), + Real32 => reader.ReadSingle(), + Real64 => reader.ReadDouble(), + _ => 0 + }; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs new file mode 100644 index 000000000..6acb79d56 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs @@ -0,0 +1,210 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible type record kinds that can be stored in a TPI or IPI stream. +/// +public enum CodeViewLeafKind : ushort +{ + /// + /// This is not really a type defined in PDB spec, but is used to indicate simple types. + /// + SimpleType = 0xffff, + +#pragma warning disable CS1591 + Modifier16T = 0x0001, + Pointer16T = 0x0002, + Array16T = 0x0003, + Class16T = 0x0004, + Structure16T = 0x0005, + Union16T = 0x0006, + Enum16T = 0x0007, + Procedure16T = 0x0008, + MFunction16T = 0x0009, + VTShape = 0x000a, + Cobol016T = 0x000b, + Cobol1 = 0x000c, + Barray16T = 0x000d, + Label = 0x000e, + Null = 0x000f, + Nottran = 0x0010, + DimArray16T = 0x0011, + VftPath16T = 0x0012, + PreComp16T = 0x0013, + EndPreComp = 0x0014, + Oem16T = 0x0015, + TypeServerSt = 0x0016, + + Skip16T = 0x0200, + ArgList16T = 0x0201, + DefArg16T = 0x0202, + List = 0x0203, + FieldList16T = 0x0204, + Derived16T = 0x0205, + BitField16T = 0x0206, + MethodList16T = 0x0207, + DimConU16T = 0x0208, + DimConLu16T = 0x0209, + DimVarU16T = 0x020a, + DimVarLu16T = 0x020b, + RefSym = 0x020c, + + BClass16T = 0x0400, + VBClass16T = 0x0401, + IVBClass16T = 0x0402, + EnumerateSt = 0x0403, + FriendFcn16T = 0x0404, + Index16T = 0x0405, + Member16T = 0x0406, + StMember16T = 0x0407, + Method16T = 0x0408, + NestType16T = 0x0409, + VFuncTab16T = 0x040a, + FriendCls16T = 0x040b, + OneMethod16T = 0x040c, + VFuncOff16T = 0x040d, + + Ti16Max = 0x1000, + + Modifier = 0x1001, + Pointer = 0x1002, + ArraySt = 0x1003, + ClassSt = 0x1004, + StructureSt = 0x1005, + UnionSt = 0x1006, + EnumSt = 0x1007, + Procedure = 0x1008, + MFunction = 0x1009, + Cobol0 = 0x100a, + BArray = 0x100b, + DimArraySt = 0x100c, + VftPath = 0x100d, + PreCompSt = 0x100e, + Oem = 0x100f, + AliasSt = 0x1010, + Oem2 = 0x1011, + + Skip = 0x1200, + ArgList = 0x1201, + DefArgSt = 0x1202, + FieldList = 0x1203, + Derived = 0x1204, + BitField = 0x1205, + MethodList = 0x1206, + DimConU = 0x1207, + DimConLu = 0x1208, + DimVarU = 0x1209, + DimVarLu = 0x120a, + + BClass = 0x1400, + VBClass = 0x1401, + IVBClass = 0x1402, + FriendFcnSt = 0x1403, + Index = 0x1404, + MemberSt = 0x1405, + StmemberSt = 0x1406, + MethodSt = 0x1407, + NestTypeSt = 0x1408, + VFuncTab = 0x1409, + FriendCls = 0x140a, + OneMethodSt = 0x140b, + VFuncOff = 0x140c, + NestTypeExSt = 0x140d, + MemberModifySt = 0x140e, + ManagedSt = 0x140f, + + StMax = 0x1500, + + TypeServer = 0x1501, + Enumerate = 0x1502, + Array = 0x1503, + Class = 0x1504, + Structure = 0x1505, + Union = 0x1506, + Enum = 0x1507, + DimArray = 0x1508, + PreComp = 0x1509, + Alias = 0x150a, + DefArg = 0x150b, + FriendFcn = 0x150c, + Member = 0x150d, + StMember = 0x150e, + Method = 0x150f, + NestType = 0x1510, + OneMethod = 0x1511, + NestTypeEx = 0x1512, + MemberModify = 0x1513, + Managed = 0x1514, + TypeServer2 = 0x1515, + + StridedArray = 0x1516, + Hlsl = 0x1517, + ModifierEx = 0x1518, + Interface = 0x1519, + BInterface = 0x151a, + Vector = 0x151b, + Matrix = 0x151c, + + VFTable = 0x151d, + EndOfLeafRecord = VFTable, + + TypeLast, + TypeMax = TypeLast - 1, + + FuncId = 0x1601, + MFuncId = 0x1602, + Buildinfo = 0x1603, + SubstrList = 0x1604, + StringId = 0x1605, + + UdtSrcLine = 0x1606, + UdtModSrcLine = 0x1607, + + IdLast, + IdMax = IdLast - 1, + + Numeric = 0x8000, + Char = 0x8000, + Short = 0x8001, + UShort = 0x8002, + Long = 0x8003, + ULong = 0x8004, + Real32 = 0x8005, + Real64 = 0x8006, + Real80 = 0x8007, + Real128 = 0x8008, + QuadWord = 0x8009, + UQuadWord = 0x800a, + Real48 = 0x800b, + Complex32 = 0x800c, + Complex64 = 0x800d, + Complex80 = 0x800e, + Complex128 = 0x800f, + VarString = 0x8010, + + OctWord = 0x8017, + UOctWord = 0x8018, + + Decimal = 0x8019, + Date = 0x801a, + Utf8String = 0x801b, + + Real16 = 0x801c, + + Pad0 = 0xf0, + Pad1 = 0xf1, + Pad2 = 0xf2, + Pad3 = 0xf3, + Pad4 = 0xf4, + Pad5 = 0xf5, + Pad6 = 0xf6, + Pad7 = 0xf7, + Pad8 = 0xf8, + Pad9 = 0xf9, + Pad10 = 0xfa, + Pad11 = 0xfb, + Pad12 = 0xfc, + Pad13 = 0xfd, + Pad14 = 0xfe, + Pad15 = 0xff, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs new file mode 100644 index 000000000..3137dabde --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs @@ -0,0 +1,40 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single record in a field list that is assigned a name. +/// +public abstract class CodeViewNamedField : CodeViewField +{ + private readonly LazyVariable _name; + + /// + /// Initializes an empty CodeView field leaf. + /// + /// The type index to assign to the leaf. + protected CodeViewNamedField(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + } + + /// + /// Gets or sets the name of the field. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the field. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => Name; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs new file mode 100644 index 000000000..097f66be0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs @@ -0,0 +1,16 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single type record in a TPI or IPI stream. +/// +public abstract class CodeViewTypeRecord : CodeViewLeaf +{ + /// + /// Initializes an empty CodeView type record. + /// + /// The type index to assign to the leaf. + protected CodeViewTypeRecord(uint typeIndex) + : base(typeIndex) + { + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs new file mode 100644 index 000000000..a88bc9c73 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs @@ -0,0 +1,33 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents an enum type. +/// +public class EnumTypeRecord : CodeViewDerivedTypeRecord +{ + /// + /// Initializes a new empty enum type. + /// + /// The type index to assign to the enum type. + protected EnumTypeRecord(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new enum type. + /// + /// The name of the enum. + /// The underlying type of all members in the enum. + /// The structural attributes assigned to the enum. + public EnumTypeRecord(Utf8String name, CodeViewTypeRecord underlyingType, StructureAttributes attributes) + : base(0) + { + Name = name; + BaseType = underlyingType; + StructureAttributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enum; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs new file mode 100644 index 000000000..4fe1a97c9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs @@ -0,0 +1,57 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single enumerate field leaf in a field list. +/// +public class EnumerateField : CodeViewNamedField +{ + private readonly LazyVariable _value; + + /// + /// Initializes an empty enumerate field leaf. + /// + /// The type index to assign to the enumerate field. + protected EnumerateField(uint typeIndex) + : base(typeIndex) + { + _value = new LazyVariable(GetValue); + } + + /// + /// Creates a new enumerate field leaf. + /// + /// The name of the field. + /// The value assigned to the field. + /// The attributes associated to the field. + public EnumerateField(Utf8String name, object value, CodeViewFieldAttributes attributes) + : base(0) + { + Name = name; + _value = new LazyVariable(value); + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enumerate; + + /// + /// Gets or sets the constant value assigned to the field. + /// + public object Value + { + get => _value.Value; + set => _value.Value = value; + } + + /// + /// Obtains the value assigned to the field. + /// + /// The value. + /// + /// This method is called upon initialization of the property. + /// + protected virtual object? GetValue() => null; + + /// + public override string ToString() => $"{Name} = {Value}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs new file mode 100644 index 000000000..5eecc0abe --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf containing a list of fields. +/// +public class FieldListLeaf : CodeViewLeaf +{ + private IList? _fields; + + /// + /// Initializes an empty field list. + /// + /// The type index to assign to the list. + protected FieldListLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty field list. + /// + public FieldListLeaf() + : base(0) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList; + + /// + /// Gets a collection of fields stored in the list. + /// + public IList Entries + { + get + { + if (_fields is null) + Interlocked.CompareExchange(ref _fields, GetEntries(), null); + return _fields; + } + } + + /// + /// Obtains the fields stored in the list. + /// + /// The fields + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs new file mode 100644 index 000000000..cc3bf9448 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs @@ -0,0 +1,43 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents an instance data member in a class or structure type. +/// +public class InstanceDataField : CodeViewDataField +{ + /// + /// Initializes an empty instance data member. + /// + /// The type index to assign to the member. + protected InstanceDataField(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new instance data member. + /// + /// The name of the member. + /// The byte offset within the class or structure that the member is stored at. + /// The data type of the member. + public InstanceDataField(Utf8String name, ulong offset, CodeViewTypeRecord dataType) + : base(dataType, name) + { + Offset = offset; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Member; + + /// + /// Gets or sets the byte offset within the class or structure that the member is stored at. + /// + public ulong Offset + { + get; + set; + } + + /// + public override string ToString() => $"+{Offset:X4}: {DataType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs new file mode 100644 index 000000000..2fbcbb55c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs @@ -0,0 +1,25 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all attributes that can be assigned to a member function. +/// +[Flags] +public enum MemberFunctionAttributes : byte +{ + /// + /// Indicates if the function is a C++ style ReturnUDT function. + /// + CxxReturnUdt = 1, + + /// + /// Indicates the function is an instance constructor. + /// + Ctor = 2, + + /// + /// Indicates the function is an instance constructor of a class with virtual bases. + /// + CtorVBase = 4 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs new file mode 100644 index 000000000..5069796fb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs @@ -0,0 +1,154 @@ +using System.Linq; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single instance member function. +/// +public class MemberFunctionLeaf : CodeViewLeaf +{ + private readonly LazyVariable _returnType; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _thisType; + private readonly LazyVariable _argumentList; + + /// + /// Initializes an empty member function. + /// + /// The type index to assign to the function. + protected MemberFunctionLeaf(uint typeIndex) + : base(typeIndex) + { + _returnType = new LazyVariable(GetReturnType); + _declaringType = new LazyVariable(GetDeclaringType); + _thisType = new LazyVariable(GetThisType); + _argumentList = new LazyVariable(GetArguments); + } + + /// + /// Creates a new member function. + /// + /// The return type of the function. + /// The declaring type of the function. + /// The argument types of the function. + public MemberFunctionLeaf(CodeViewTypeRecord returnType, CodeViewTypeRecord declaringType, ArgumentListLeaf arguments) + : base(0) + { + _returnType = new LazyVariable(returnType); + _declaringType = new LazyVariable(declaringType); + _thisType = new LazyVariable(default(CodeViewTypeRecord)); + _argumentList = new LazyVariable(arguments); + CallingConvention = CodeViewCallingConvention.NearC; + Attributes = 0; + ThisAdjuster = 0; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MFunction; + + /// + /// Gets or sets the return type of the function. + /// + public CodeViewTypeRecord? ReturnType + { + get => _returnType.Value; + set => _returnType.Value = value; + } + + /// + /// Gets or sets the type that declares this member function. + /// + public CodeViewTypeRecord? DeclaringType + { + get => _declaringType.Value; + set => _declaringType.Value = value; + } + + /// + /// Gets or sets the type of the this pointer that is used to access the member function. + /// + public CodeViewTypeRecord? ThisType + { + get => _thisType.Value; + set => _thisType.Value = value; + } + + /// + /// Gets or sets the convention that is used when calling the member function. + /// + public CodeViewCallingConvention CallingConvention + { + get; + set; + } + + /// + /// Gets or sets the attributes associated to the function. + /// + public MemberFunctionAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the list of types of the parameters that this function defines. + /// + public ArgumentListLeaf? Arguments + { + get => _argumentList.Value; + set => _argumentList.Value = value; + } + + /// + /// Gets or sets the offset to adjust the this pointer with before devirtualization of this method. + /// + public uint ThisAdjuster + { + get; + set; + } + + /// + /// Obtains the return type of the function. + /// + /// The return type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetReturnType() => null; + + /// + /// Obtains the declaring type of the function. + /// + /// The declaring type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetDeclaringType() => null; + + /// + /// Obtains the this-type of the function. + /// + /// The this-type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetThisType() => null; + + /// + /// Obtains the argument types of the function. + /// + /// The argument types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ArgumentListLeaf? GetArguments() => null; + + /// + public override string ToString() + { + string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); + return $"{CallingConvention} {ReturnType} {DeclaringType}::*({args})"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs new file mode 100644 index 000000000..447296798 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs @@ -0,0 +1,94 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents one single entry in a list of overloaded methods. +/// +public class MethodListEntry +{ + private readonly LazyVariable _function; + + /// + /// Initializes an empty method list entry. + /// + protected MethodListEntry() + { + _function = new LazyVariable(GetFunction); + } + + /// + /// Creates a new method list entry. + /// + /// The attributes associated to this method. + /// The referenced function. + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function) + { + Attributes = attributes; + _function = new LazyVariable(function); + VTableOffset = 0; + } + + /// + /// Creates a new method list entry. + /// + /// The attributes associated to this method. + /// The referenced function. + /// The offset to the slot the virtual function table that this method occupies. + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function, uint vTableOffset) + { + Attributes = attributes; + _function = new LazyVariable(function); + VTableOffset = vTableOffset; + } + + /// + /// Gets or sets the attributes associated to this method. + /// + public CodeViewFieldAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the function that is referenced by this method. + /// + public MemberFunctionLeaf? Function + { + get => _function.Value; + set => _function.Value = value; + } + + /// + /// Gets a value indicating whether the function is a newly introduced virtual function. + /// + public bool IsIntroducingVirtual => + (Attributes & CodeViewFieldAttributes.IntroducingVirtual) != 0 + || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) != 0; + + /// + /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function + /// table that this method occupies. + /// + public uint VTableOffset + { + get; + set; + } + + /// + /// Obtains the function that this method references. + /// + /// The function. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MemberFunctionLeaf? GetFunction() => null; + + /// + public override string ToString() + { + return IsIntroducingVirtual + ? $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}, {nameof(VTableOffset)}: {VTableOffset}" + : $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs new file mode 100644 index 000000000..f9538bac3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf record containing a list of overloaded methods. +/// +public class MethodListLeaf : CodeViewLeaf +{ + private IList? _entries; + + /// + /// Initializes an empty method list. + /// + /// The type index to assign to the list. + protected MethodListLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty method list. + /// + public MethodListLeaf() + : base(0) + { + } + + /// + /// Creates a new method list. + /// + /// The methods to include. + public MethodListLeaf(params MethodListEntry[] entries) + : base(0) + { + _entries = entries.ToList(); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MethodList; + + /// + /// Gets a collection of methods stored in the list. + /// + public IList Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the methods stored in the list. + /// + /// The methods + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs new file mode 100644 index 000000000..3c221e900 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs @@ -0,0 +1,26 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible modifiers that can be added to a type using a Modifier type record in a +/// TPI or IPI stream. +/// +[Flags] +public enum ModifierAttributes : ushort +{ + /// + /// Indicates the type is marked as const. + /// + Const = 1, + + /// + /// Indicates the type is marked as volatile. + /// + Volatile = 2, + + /// + /// Indicates the type is marked as unaligned. + /// + Unaligned = 4, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs new file mode 100644 index 000000000..c70e50ed1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs @@ -0,0 +1,94 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type that is annotated with extra modifiers. +/// +public class ModifierTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _baseType; + + /// + /// Initializes a new empty modifier type. + /// + /// The type index to assign to the modifier type. + protected ModifierTypeRecord(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new modified type. + /// + /// The type to be modified. + /// The attributes describing the shape of the pointer. + public ModifierTypeRecord(CodeViewTypeRecord type, ModifierAttributes attributes) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Modifier; + + /// + /// Gets or sets the type that is annotated. + /// + public CodeViewTypeRecord BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the annotations that were added to the type. + /// + public ModifierAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether the type is marked as const. + /// + public bool IsConst + { + get => (Attributes & ModifierAttributes.Const) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Const) + | (value ? ModifierAttributes.Const : 0); + } + + /// + /// Gets or sets a value indicating whether the type is marked as volatile. + /// + public bool IsVolatile + { + get => (Attributes & ModifierAttributes.Volatile) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Volatile) + | (value ? ModifierAttributes.Volatile : 0); + } + + /// + /// Gets or sets a value indicating whether the type is marked as unaligned. + /// + public bool IsUnaligned + { + get => (Attributes & ModifierAttributes.Unaligned) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Unaligned) + | (value ? ModifierAttributes.Unaligned : 0); + } + + /// + /// Obtains the base type of the modifier type. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => $"{BaseType} {Attributes}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs new file mode 100644 index 000000000..91bca6ee3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs @@ -0,0 +1,69 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a field in a type that references a nested type definition. +/// +public class NestedTypeField : CodeViewNamedField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty nested type. + /// + /// The type index to assign to the nested type field. + protected NestedTypeField(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetNestedType); + } + + /// + /// Creates a new nested type field. + /// + /// The definition of the nested type + /// The name of the nested type. + public NestedTypeField(CodeViewTypeRecord type, Utf8String name) + : base(0) + { + _type = new LazyVariable(type); + Name = name; + Attributes = 0; + } + + /// + /// Creates a new nested type (extended) field. + /// + /// The definition of the nested type + /// The name of the nested type. + /// The attributes assigned to the type. + public NestedTypeField(CodeViewTypeRecord type, Utf8String name, CodeViewFieldAttributes attributes) + : base(0) + { + _type = new LazyVariable(type); + Name = name; + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => Attributes == 0 + ? CodeViewLeafKind.NestType + : CodeViewLeafKind.NestTypeEx; + + /// + /// Gets or sets the definition of the referenced nested type. + /// + public CodeViewTypeRecord? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Obtains the definition of the nested type. + /// + /// The type + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetNestedType() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs new file mode 100644 index 000000000..08bdb113f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs @@ -0,0 +1,80 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single method in a type. +/// +public class NonOverloadedMethod : CodeViewNamedField +{ + private readonly LazyVariable _function; + + /// + /// Initializes an empty non-overloaded method. + /// + /// The type index to assign to the method. + protected NonOverloadedMethod(uint typeIndex) + : base(typeIndex) + { + _function = new LazyVariable(GetFunction); + } + + /// + /// Creates a new overloaded method. + /// + /// The name of the method. + /// The attributes associated to the method. + /// The function that is referenced by the method. + public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, MemberFunctionLeaf function) + : base(0) + { + _function = new LazyVariable(function); + Attributes = attributes; + Name = name; + } + + /// + /// Creates a new overloaded method. + /// + /// The name of the method. + /// The attributes associated to the method. + /// The offset to the slot the virtual function table that this method occupies. + /// The function that is referenced by the method. + public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, uint vTableOffset, MemberFunctionLeaf function) + : base(0) + { + _function = new LazyVariable(function); + Attributes = attributes; + Name = name; + VTableOffset = vTableOffset; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.OneMethod; + + /// + /// Gets or sets the function that is referenced by this method. + /// + public MemberFunctionLeaf? Function + { + get => _function.Value; + set => _function.Value = value; + } + + /// + /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function + /// table that this method occupies. + /// + public uint VTableOffset + { + get; + set; + } + + /// + /// Obtains the function that this method references. + /// + /// The function. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MemberFunctionLeaf? GetFunction() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs new file mode 100644 index 000000000..a6c17cea0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs @@ -0,0 +1,67 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a method that is overloaded by one or more functions. +/// +public class OverloadedMethod : CodeViewNamedField +{ + private readonly LazyVariable _methods; + + /// + /// Initializes an empty overloaded method. + /// + /// The type index to assign to the method. + protected OverloadedMethod(uint typeIndex) + : base(typeIndex) + { + _methods = new LazyVariable(GetMethods); + } + + /// + /// Creates a new empty overloaded method. + /// + public OverloadedMethod() + : base(0) + { + _methods = new LazyVariable(new MethodListLeaf()); + } + + /// + /// Creates a new overloaded method. + /// + public OverloadedMethod(MethodListLeaf methods) + : base(0) + { + _methods = new LazyVariable(methods); + } + + /// + /// Creates a new overloaded method. + /// + public OverloadedMethod(params MethodListEntry[] methods) + : base(0) + { + _methods = new LazyVariable(new MethodListLeaf(methods)); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Method; + + /// + /// Gets or sets a list of methods that were overloaded. + /// + public MethodListLeaf? Methods + { + get => _methods.Value; + set => _methods.Value = value; + } + + /// + /// Obtains the list of methods that were overloaded. + /// + /// The methods. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MethodListLeaf? GetMethods() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs new file mode 100644 index 000000000..a1c9ac743 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs @@ -0,0 +1,145 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible flags that can be assigned to a pointer type in a TPI or IPI stream. +/// +[Flags] +public enum PointerAttributes : uint +{ + /// + /// Indicates the pointer is a 16 bit pointer. + /// + Near16 = 0b0000000000_000_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 16:16 far pointer. + /// + Far16 = 0b0000000000_000_000000_00000_000_000001, + + /// + /// Indicates the pointer is a 16:16 huge pointer. + /// + Huge16 = 0b0000000000_000_000000_00000_000_000010, + + /// + /// Indicates the pointer is a based on segment. + /// + BasedOnSegment = 0b0000000000_000_000000_00000_000_000011, + + /// + /// Indicates the pointer is a based on value of base. + /// + BasedOnValue = 0b0000000000_000_000000_00000_000_000100, + + /// + /// Indicates the pointer is a based on segment value of base. + /// + BasedOnSegmentValue = 0b0000000000_000_000000_00000_000_000101, + + /// + /// Indicates the pointer is a based on address of base. + /// + BasedOnAddress = 0b0000000000_000_000000_00000_000_000110, + + /// + /// Indicates the pointer is a based on segment address of base. + /// + BasedOnSegmentAddress = 0b0000000000_000_000000_00000_000_000111, + + /// + /// Indicates the pointer is a based on type. + /// + BasedOnType = 0b0000000000_000_000000_00000_000_001000, + + /// + /// Indicates the pointer is a based on self. + /// + BasedOnSelf = 0b0000000000_000_000000_00000_000_001001, + + /// + /// Indicates the pointer is a 32 bit pointer. + /// + Near32 = 0b0000000000_000_000000_00000_000_001010, + + /// + /// Indicates the pointer is a 16:32 pointer. + /// + Far32 = 0b0000000000_000_000000_00000_000_001011, + + /// + /// Indicates the pointer is a 64 bit pointer. + /// + Near64 = 0b0000000000_000_000000_00000_000_001100, + + /// + /// Provides the bit-mask for extracting the pointer kind from the flags. + /// + KindMask = 0b0000000000_000_000000_00000_000_11111, + + /// + /// Indicates the pointer is an "old" reference. + /// + LValueReference = 0b0000000000_000_000000_00000_001_00000, + + /// + /// Indicates the pointer is a pointer to data member. + /// + PointerToDataMember = 0b0000000000_000_000000_00000_010_00000, + + /// + /// Indicates the pointer is a pointer to member function. + /// + PointerToMemberFunction = 0b0000000000_000_000000_00000_011_00000, + + /// + /// Indicates the pointer is an r-value reference. + /// + RValueReference = 0b0000000000_000_000000_00000_100_00000, + + /// + /// Provides the bit-mask for extracting the pointer mode from the flags. + /// + ModeMask = 0b0000000000_000_000000_00000_111_00000, + + /// + /// Indicates the pointer is a "flat" pointer. + /// + Flat32 = 0b0000000000_000_000000_00001_000_00000, + + /// + /// Indicates the pointer is marked volatile. + /// + Volatile = 0b0000000000_000_000000_00010_000_00000, + + /// + /// Indicates the pointer is marked const. + /// + Const = 0b0000000000_000_000000_00100_000_00000, + + /// + /// Indicates the pointer is marked unaligned. + /// + Unaligned = 0b0000000000_000_000000_01000_000_00000, + + /// + /// Indicates the pointer is marked restrict. + /// + Restrict = 0b0000000000_000_000000_10000_000_00000, + + /// + /// Indicates the pointer is a WinRT smart pointer. + /// + WinRTSmartPointer = 0b0000000000_001_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 'this' pointer of a member function with ref qualifier. + /// + LValueRefThisPointer = 0b0000000000_010_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 'this' pointer of a member function with ref qualifier. + /// + RValueRefThisPointer = 0b0000000000_100_000000_00000_000_00000 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs new file mode 100644 index 000000000..740e792cd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs @@ -0,0 +1,356 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a pointer type in a TPI or IPI stream. +/// +public class PointerTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _baseType; + + /// + /// Initializes a new empty pointer type. + /// + /// The type index to assign to the type. + protected PointerTypeRecord(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new pointer type. + /// + /// The referent type. + /// The attributes describing the shape of the pointer. + public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + } + + /// + /// Creates a new pointer type. + /// + /// The referent type. + /// The attributes describing the shape of the pointer. + /// The size of the pointer. + public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes, byte size) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + Size = size; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Pointer; + + /// + /// Gets or sets the referent type of the pointer. + /// + public CodeViewTypeRecord BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the attributes describing the shape of the pointer type. + /// + public PointerAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the kind of the pointer. + /// + public PointerAttributes Kind + { + get => Attributes & PointerAttributes.KindMask; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) | (value & PointerAttributes.KindMask); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16 bit pointer. + /// + public bool IsNear16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:16 far pointer. + /// + public bool IsFar16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Far16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Far16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:16 huge pointer. + /// + public bool IsHuge16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Huge16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Huge16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment. + /// + public bool IsBasedOnSegment + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegment; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegment : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on value of base. + /// + public bool IsBasedOnValue + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnValue; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnValue : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment value of base. + /// + public bool IsBasedOnSegmentValue + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegmentValue; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegmentValue : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on address of base. + /// + public bool IsBasedOnAddress + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnAddress; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnAddress : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment address of base. + /// + public bool IsBasedOnSegmentAddress + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegmentAddress; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegmentAddress : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on type. + /// + public bool IsBasedOnType + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnType; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnType : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on self. + /// + public bool IsBasedOnSelf + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSelf; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSelf : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 32 bit pointer. + /// + public bool IsNear32 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near32; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:32 pointer. + /// + public bool IsFar32 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Far32; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Far32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 64 bit pointer. + /// + public bool IsNear64 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near64; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near64 : 0); + } + + /// + /// Gets or sets the mode of the pointer. + /// + public PointerAttributes Mode + { + get => Attributes & PointerAttributes.ModeMask; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) | (value & PointerAttributes.ModeMask); + } + + /// + /// Gets or sets a value indicating whether the pointer is an "old" reference. + /// + public bool IsLValueReference + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.LValueReference; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.LValueReference : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a pointer to data member. + /// + public bool IsPointerToDataMember + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.PointerToDataMember; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.PointerToDataMember : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a pointer to member function. + /// + public bool IsPointerToMemberFunction + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.PointerToMemberFunction; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.PointerToMemberFunction : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is an r-value reference. + /// + public bool IsRValueReference + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.RValueReference; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.RValueReference : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a "flat" pointer. + /// + public bool IsFlat32 + { + get => (Attributes & PointerAttributes.Flat32) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Flat32) + | (value ? PointerAttributes.Flat32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked volatile. + /// + public bool IsVolatile + { + get => (Attributes & PointerAttributes.Volatile) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Volatile) + | (value ? PointerAttributes.Volatile : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked const. + /// + public bool IsConst + { + get => (Attributes & PointerAttributes.Const) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Const) + | (value ? PointerAttributes.Const : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked unaligned. + /// + public bool IsUnaligned + { + get => (Attributes & PointerAttributes.Unaligned) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Unaligned) + | (value ? PointerAttributes.Unaligned : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked restrict. + /// + public bool IsRestrict + { + get => (Attributes & PointerAttributes.Restrict) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Restrict) + | (value ? PointerAttributes.Restrict : 0); + } + + /// + /// Gets or sets the size of the pointer. + /// + public byte Size + { + get => (byte) (((uint) Attributes >> 0xD) & 0b111111); + set => Attributes = (PointerAttributes) (((uint) Attributes & ~(0b111111u << 0xD)) + | (((uint) value & 0b111111) << 0xD)); + } + + /// + /// Gets or sets a value indicating whether the pointer is a WinRT smart pointer. + /// + public bool IsWinRTSmartPointer + { + get => (Attributes & PointerAttributes.WinRTSmartPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.WinRTSmartPointer) + | (value ? PointerAttributes.WinRTSmartPointer : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 'this' pointer of a member function with ref qualifier. + /// + public bool IsLValueRefThisPointer + { + get => (Attributes & PointerAttributes.LValueRefThisPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.LValueRefThisPointer) + | (value ? PointerAttributes.LValueRefThisPointer : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 'this' pointer of a member function with ref qualifier. + /// + public bool IsRValueRefThisPointer + { + get => (Attributes & PointerAttributes.RValueRefThisPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.RValueRefThisPointer) + | (value ? PointerAttributes.RValueRefThisPointer : 0); + } + + /// + /// Obtains the base type of the pointer. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => $"({BaseType})*"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs new file mode 100644 index 000000000..b973fcccb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs @@ -0,0 +1,101 @@ +using System.Linq; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a function pointer or procedure type. +/// +public class ProcedureTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _returnType; + private readonly LazyVariable _argumentList; + + /// + /// Initializes an empty procedure type. + /// + /// The type index to assign to the type. + protected ProcedureTypeRecord(uint typeIndex) + : base(typeIndex) + { + _returnType = new LazyVariable(GetReturnType); + _argumentList = new LazyVariable(GetArguments); + } + + /// + /// Creates a new procedure type. + /// + /// The convention to use when calling the function pointed by values of this type. + /// The return type of the function. + /// The argument type list of the function. + public ProcedureTypeRecord(CodeViewCallingConvention callingConvention, CodeViewTypeRecord returnType, ArgumentListLeaf arguments) + : base(0) + { + CallingConvention = callingConvention; + _returnType = new LazyVariable(returnType); + _argumentList = new LazyVariable(arguments); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Procedure; + + /// + /// Gets or sets the return type of the function. + /// + public CodeViewTypeRecord? ReturnType + { + get => _returnType.Value; + set => _returnType.Value = value; + } + + /// + /// Gets or sets the convention that is used when calling the member function. + /// + public CodeViewCallingConvention CallingConvention + { + get; + set; + } + + /// + /// Gets or sets the attributes associated to the function. + /// + public MemberFunctionAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the list of types of the parameters that this function defines. + /// + public ArgumentListLeaf? Arguments + { + get => _argumentList.Value; + set => _argumentList.Value = value; + } + + /// + /// Obtains the return type of the procedure. + /// + /// The return type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetReturnType() => null; + + /// + /// Obtains the argument types of the procedure.. + /// + /// The argument types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ArgumentListLeaf? GetArguments() => null; + + /// + public override string ToString() + { + string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); + return $"{CallingConvention} {ReturnType} *({args})"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs new file mode 100644 index 000000000..a8a54409c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedArgumentListLeaf : ArgumentListLeaf +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a argument list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the list. + /// The input stream to read from. + public SerializedArgumentListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + protected override IList GetArgumentTypes() + { + var reader = _reader.Fork(); + + int count = reader.ReadInt32(); + var result = new List(count); + + for (int i = 0; i < count; i++) + { + uint typeIndex = reader.ReadUInt32(); + if (!_context.ParentImage.TryGetLeafRecord(typeIndex, out var leaf) || leaf is not CodeViewTypeRecord t) + { + _context.Parameters.ErrorListener.BadImage( + $"Argument list {TypeIndex:X8} contains an invalid argument type index {typeIndex:X8}."); + return result; + } + + result.Add(t); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs new file mode 100644 index 000000000..273df7536 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs @@ -0,0 +1,52 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedArrayTypeRecord : ArrayTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _elementTypeIndex; + private readonly uint _indexTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an array type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedArrayTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _elementTypeIndex = reader.ReadUInt32(); + _indexTypeIndex = reader.ReadUInt32(); + Length = Convert.ToUInt64(ReadNumeric(ref reader)); + _nameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetElementType() + { + return _context.ParentImage.TryGetLeafRecord(_elementTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Array type {TypeIndex:X8} contains an invalid element type index {_elementTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetIndexType() + { + return _context.ParentImage.TryGetLeafRecord(_indexTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Array type {TypeIndex:X8} contains an invalid index type index {_indexTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs new file mode 100644 index 000000000..325e73010 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs @@ -0,0 +1,37 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedBaseClassField : BaseClassField +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a base class from the provided input stream. + /// + /// The reading context in which the class is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedBaseClassField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _baseTypeIndex = reader.ReadUInt32(); + Offset = Convert.ToUInt64(ReadNumeric(ref reader)); + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Base class {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs new file mode 100644 index 000000000..e18e87dfe --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs @@ -0,0 +1,36 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedBitFieldTypeRecord : BitFieldTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a bit field type from the provided input stream. + /// + /// The reading context in which the bit field type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedBitFieldTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Length = reader.ReadByte(); + Position = reader.ReadByte(); + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Bit field type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs new file mode 100644 index 000000000..3773352ff --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs @@ -0,0 +1,83 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedClassTypeRecord : ClassTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _baseTypeIndex; + private readonly uint _fieldIndex; + private readonly uint _vTableShapeIndex; + private readonly BinaryStreamReader _nameReader; + private readonly BinaryStreamReader _uniqueNameReader; + + /// + /// Reads a class type from the provided input stream. + /// + /// The kind of type that is being read. + /// The reading context in which the type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedClassTypeRecord(CodeViewLeafKind kind, PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(kind, typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _fieldIndex = reader.ReadUInt32(); + _baseTypeIndex = reader.ReadUInt32(); + _vTableShapeIndex = reader.ReadUInt32(); + + Size = (uint) ReadNumeric(ref reader); + + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + _uniqueNameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + if (_baseTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid underlying enum type index {_baseTypeIndex:X8}."); + } + + /// + protected override FieldListLeaf? GetFields() + { + if (_fieldIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + } + + /// + protected override VTableShapeLeaf? GetVTableShape() + { + if (_vTableShapeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_vTableShapeIndex, out var leaf) && leaf is VTableShapeLeaf shape + ? shape + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid VTable shape index {_fieldIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs new file mode 100644 index 000000000..e671bb176 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs @@ -0,0 +1,56 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedEnumTypeRecord : EnumTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _underlyingType; + private readonly uint _fieldIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a constant symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedEnumTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _underlyingType = reader.ReadUInt32(); + _fieldIndex = reader.ReadUInt32(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}."); + } + + /// + protected override FieldListLeaf? GetFields() + { + if (_fieldIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is FieldListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Enum type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs new file mode 100644 index 000000000..91457f27f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs @@ -0,0 +1,32 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedEnumerateField : EnumerateField +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a enumerate field list from the provided input stream. + /// + /// The reading context in which the enumerate field is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedEnumerateField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + + // We need to eagerly initialize the value because it is the only way to know how large the leaf is. + Value = ReadNumeric(ref reader); + + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs new file mode 100644 index 000000000..2eddbc7d3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +internal class SerializedFieldListLeaf : FieldListLeaf +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a field list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedFieldListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList; + + protected override IList GetEntries() + { + var reader = _reader.Fork(); + var result = new List(); + + while (reader.CanRead(sizeof(ushort))) + { + // Skip padding bytes. + while (reader.CanRead(sizeof(byte))) + { + var b = (CodeViewLeafKind) reader.ReadByte(); + if (b < CodeViewLeafKind.Pad0) + { + reader.Offset--; + break; + } + } + + if (!reader.CanRead(sizeof(byte))) + break; + + // Read field. + var leaf = FromReaderNoHeader(_context, 0, ref reader); + if (leaf is CodeViewField field) + result.Add(field); + else + _context.Parameters.ErrorListener.BadImage($"Field list contains a non-field leaf {leaf.LeafKind}."); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs new file mode 100644 index 000000000..c5fcf252e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs @@ -0,0 +1,49 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedInstanceDataField : InstanceDataField +{ + private readonly PdbReaderContext _context; + private readonly uint _dataTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an instance data member list from the provided input stream. + /// + /// The reading context in which the member is situated in. + /// The type index to assign to the member. + /// The input stream to read from. + public SerializedInstanceDataField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _dataTypeIndex = reader.ReadUInt32(); + + // We need to eagerly initialize the offset because it is the only way to know how large the leaf is. + Offset = Convert.ToUInt64(ReadNumeric(ref reader)); + + _context = context; + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetDataType() + { + if (_dataTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Instance data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs new file mode 100644 index 000000000..1b0cc2253 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs @@ -0,0 +1,75 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMemberFunctionLeaf : MemberFunctionLeaf +{ + private readonly PdbReaderContext _context; + private readonly uint _returnTypeIndex; + private readonly uint _declaringTypeIndex; + private readonly uint _thisTypeIndex; + private readonly ushort _parameterCount; + private readonly uint _argumentListIndex; + + /// + /// Reads a member function from the provided input stream. + /// + /// The reading context in which the function is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedMemberFunctionLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _returnTypeIndex = reader.ReadUInt32(); + _declaringTypeIndex = reader.ReadUInt32(); + _thisTypeIndex = reader.ReadUInt32(); + CallingConvention = (CodeViewCallingConvention) reader.ReadByte(); + Attributes = (MemberFunctionAttributes) reader.ReadByte(); + _parameterCount = reader.ReadUInt16(); + _argumentListIndex = reader.ReadUInt32(); + ThisAdjuster = reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetReturnType() + { + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetDeclaringType() + { + return _context.ParentImage.TryGetLeafRecord(_declaringTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid declaring type index {_declaringTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetThisType() + { + return _context.ParentImage.TryGetLeafRecord(_thisTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid this-type index {_thisTypeIndex:X8}."); + } + + /// + protected override ArgumentListLeaf? GetArguments() + { + if (_argumentListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs new file mode 100644 index 000000000..af17da782 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMethodListEntry : MethodListEntry +{ + private readonly PdbReaderContext _context; + private readonly uint _functionIndex; + + /// + /// Reads a member function from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The input stream to read from. + public SerializedMethodListEntry(PdbReaderContext context, ref BinaryStreamReader reader) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + reader.ReadUInt16(); // padding + _functionIndex = reader.ReadUInt32(); + VTableOffset = IsIntroducingVirtual ? reader.ReadUInt32() : 0; + } + + /// + protected override MemberFunctionLeaf? GetFunction() + { + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Method list entry contains an invalid function type index {_functionIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs new file mode 100644 index 000000000..36927a164 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMethodListLeaf : MethodListLeaf +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a method list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedMethodListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + protected override IList GetEntries() + { + var result = new List(); + + var reader = _reader.Fork(); + while (reader.CanRead(8)) + result.Add(new SerializedMethodListEntry(_context, ref reader)); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs new file mode 100644 index 000000000..a6c9b5bf3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedModifierTypeRecord : ModifierTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a pointer type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedModifierTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Attributes = (ModifierAttributes) reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Modifier type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs new file mode 100644 index 000000000..9ebefafc0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs @@ -0,0 +1,41 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedNestedTypeField : NestedTypeField +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a nested type field from the provided input stream. + /// + /// The reading context in which the field is situated in. + /// The index to assign to the field. + /// The input stream to read from. + public SerializedNestedTypeField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _typeIndex = reader.ReadUInt32(); + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetNestedType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Nested type {TypeIndex:X8} contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs new file mode 100644 index 000000000..08347ed9d --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs @@ -0,0 +1,43 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedNonOverloadedMethod : NonOverloadedMethod +{ + private readonly PdbReaderContext _context; + private readonly uint _functionIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a non-overloaded method from the provided input stream. + /// + /// The reading context in which the method is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedNonOverloadedMethod(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _functionIndex = reader.ReadUInt32(); + if (IsIntroducingVirtual) + VTableOffset = reader.ReadUInt32(); + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override MemberFunctionLeaf? GetFunction() + { + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Method {TypeIndex:X8} contains an invalid function type index {_functionIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs new file mode 100644 index 000000000..3342850e3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs @@ -0,0 +1,45 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedOverloadedMethod : OverloadedMethod +{ + private readonly PdbReaderContext _context; + private readonly ushort _functionCount; + private readonly uint _methodListIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an overloaded method from the provided input stream. + /// + /// The reading context in which the method is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedOverloadedMethod(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _functionCount = reader.ReadUInt16(); + _methodListIndex = reader.ReadUInt32(); + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override MethodListLeaf? GetMethods() + { + if (_methodListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_methodListIndex, out var leaf) && leaf is MethodListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Overloaded method {TypeIndex:X8} contains an invalid field list index {_methodListIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs new file mode 100644 index 000000000..74a481134 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs @@ -0,0 +1,38 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedPointerTypeRecord : PointerTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a pointer type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedPointerTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Attributes = (PointerAttributes) reader.ReadUInt32(); + + // TODO: member pointer info + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Pointer {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs new file mode 100644 index 000000000..90465a8a9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs @@ -0,0 +1,52 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedProcedureTypeRecord : ProcedureTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _returnTypeIndex; + private readonly ushort _parameterCount; + private readonly uint _argumentListIndex; + + /// + /// Reads a procedure type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedProcedureTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _returnTypeIndex = reader.ReadUInt32(); + CallingConvention = (CodeViewCallingConvention) reader.ReadByte(); + Attributes = (MemberFunctionAttributes) reader.ReadByte(); + _parameterCount = reader.ReadUInt16(); + _argumentListIndex = reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetReturnType() + { + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Procedure type {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); + } + + /// + protected override ArgumentListLeaf? GetArguments() + { + if (_argumentListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Procedure type {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs new file mode 100644 index 000000000..4196ac5c9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs @@ -0,0 +1,45 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedStaticDataField : StaticDataField +{ + private readonly PdbReaderContext _context; + private readonly uint _dataTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a static data member list from the provided input stream. + /// + /// The reading context in which the member is situated in. + /// The type index to assign to the member. + /// The input stream to read from. + public SerializedStaticDataField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _dataTypeIndex = reader.ReadUInt32(); + + _context = context; + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetDataType() + { + if (_dataTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Static data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs new file mode 100644 index 000000000..3a0169a39 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs @@ -0,0 +1,55 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedUnionTypeRecord : UnionTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _fieldIndex; + private readonly BinaryStreamReader _nameReader; + private readonly BinaryStreamReader _uniqueNameReader; + + /// + /// Reads a union type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedUnionTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _fieldIndex = reader.ReadUInt32(); + + Size = Convert.ToUInt64(ReadNumeric(ref reader)); + + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + _uniqueNameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String(); + + /// + protected override FieldListLeaf? GetFields() + { + if (_fieldIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Union type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs new file mode 100644 index 000000000..5be9b1165 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs @@ -0,0 +1,55 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVBaseClassField : VBaseClassField +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + private readonly uint _basePointerIndex; + + /// + /// Reads a virtual base class from the provided input stream. + /// + /// The reading context in which the class is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + /// true if the field is an indirect virtual base class, false otherwise. + public SerializedVBaseClassField( + PdbReaderContext context, + uint typeIndex, + ref BinaryStreamReader reader, + bool isIndirect) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _baseTypeIndex = reader.ReadUInt32(); + _basePointerIndex = reader.ReadUInt32(); + PointerOffset = Convert.ToUInt64(ReadNumeric(ref reader)); + TableOffset = Convert.ToUInt64(ReadNumeric(ref reader)); + IsIndirect = isIndirect; + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual base class {TypeIndex:X8} contains an invalid base type index {_baseTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetBasePointerType() + { + return _context.ParentImage.TryGetLeafRecord(_basePointerIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual base class {TypeIndex:X8} contains an invalid base pointer type index {_basePointerIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs new file mode 100644 index 000000000..66ed003c7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVTableField : VTableField +{ + private readonly PdbReaderContext _context; + private readonly uint _pointerTypeIndex; + + /// + /// Reads a virtual function table field from the provided input stream. + /// + /// The reading context in which the field is situated in. + /// The type index to assign to the field. + /// The input stream to read from. + public SerializedVTableField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + reader.ReadUInt16(); // padding + _pointerTypeIndex = reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetPointerType() + { + return _context.ParentImage.TryGetLeafRecord(_pointerTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual function table type {TypeIndex:X8} contains an invalid pointer type index {_pointerTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs new file mode 100644 index 000000000..b8b7f0396 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVTableShapeLeaf : VTableShapeLeaf +{ + private readonly ushort _count; + private readonly BinaryStreamReader _entriesReader; + + /// + /// Reads a virtual function table shape from the provided input stream. + /// + /// The reading context in which the shape is situated in. + /// The index to assign to the shape. + /// The input stream to read from. + public SerializedVTableShapeLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _count = reader.ReadUInt16(); + _entriesReader = reader; + } + + /// + protected override IList GetEntries() + { + var result = new List(_count); + var reader = _entriesReader.Fork(); + + // Entries are stored as 4-bit values. + byte currentByte = 0; + for (int i = 0; i < _count; i++) + { + if (i % 2 == 0) + { + currentByte = reader.ReadByte(); + result.Add((VTableShapeEntry) (currentByte & 0xF)); + } + else + { + result.Add((VTableShapeEntry) ((currentByte >> 4) & 0xF)); + } + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs new file mode 100644 index 000000000..77cbbff33 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs @@ -0,0 +1,250 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all basic type kinds that can be used as a type index. +/// +/// +/// Reference: https://llvm.org/docs/PDB/TpiStream.html +/// +public enum SimpleTypeKind : uint +{ + /// + /// Indicates the type index indicates the absence of a specific type or type category. + /// + None = 0x0000, + + /// + /// Indicates the type index references the void type. + /// + Void = 0x0003, + + /// + /// Indicates the type index references a type that is not translated by CVPack. + /// + NotTranslated = 0x0007, + + /// + /// Indicates the type index references the OLE/COM HRESULT type. + /// + HResult = 0x0008, + + /// + /// Indicates the type index references the 8 bit signed character type. + /// + SignedCharacter = 0x0010, + + /// + /// Indicates the type index references the 8 bit unsigned character type. + /// + UnsignedCharacter = 0x0020, + + /// + /// Indicates the type index references the narrow character type. + /// + NarrowCharacter = 0x0070, + + /// + /// Indicates the type index references the wide character type. + /// + WideCharacter = 0x0071, + + /// + /// Indicates the type index references the char16_t type. + /// + Character16 = 0x007a, + + /// + /// Indicates the type index references the char32_t type. + /// + Character32 = 0x007b, + + /// + /// Indicates the type index references the char8_t type. + /// + Character8 = 0x007c, + + /// + /// Indicates the type index references the 8 bit signed int type. + /// + SByte = 0x0068, + + /// + /// Indicates the type index references the 8 bit unsigned int type. + /// + Byte = 0x0069, + + /// + /// Indicates the type index references the 16 bit signed type. + /// + Int16Short = 0x0011, + + /// + /// Indicates the type index references the 16 bit unsigned type. + /// + UInt16Short = 0x0021, + + /// + /// Indicates the type index references the 16 bit signed int type. + /// + Int16 = 0x0072, + + /// + /// Indicates the type index references the 16 bit unsigned int type. + /// + UInt16 = 0x0073, + + /// + /// Indicates the type index references the 32 bit signed type. + /// + Int32Long = 0x0012, + + /// + /// Indicates the type index references the 32 bit unsigned type. + /// + UInt32Long = 0x0022, + + /// + /// Indicates the type index references the 32 bit signed int type. + /// + Int32 = 0x0074, + + /// + /// Indicates the type index references the 32 bit unsigned int type. + /// + UInt32 = 0x0075, + + /// + /// Indicates the type index references the 64 bit signed type. + /// + Int64Quad = 0x0013, + + /// + /// Indicates the type index references the 64 bit unsigned type. + /// + UInt64Quad = 0x0023, + + /// + /// Indicates the type index references the 64 bit signed int type. + /// + Int64 = 0x0076, + + /// + /// Indicates the type index references the 64 bit unsigned int type. + /// + UInt64 = 0x0077, + + /// + /// Indicates the type index references the 128 bit signed int type. + /// + Int128Oct = 0x0014, + + /// + /// Indicates the type index references the 128 bit unsigned int type. + /// + UInt128Oct = 0x0024, + + /// + /// Indicates the type index references the 128 bit signed int type. + /// + Int128 = 0x0078, + + /// + /// Indicates the type index references the 128 bit unsigned int type. + /// + UInt128 = 0x0079, + + /// + /// Indicates the type index references the 16 bit real type. + /// + Float16 = 0x0046, + + /// + /// Indicates the type index references the 32 bit real type. + /// + Float32 = 0x0040, + + /// + /// Indicates the type index references the 32 bit PP real type. + /// + Float32PartialPrecision = 0x0045, + + /// + /// Indicates the type index references the 48 bit real type. + /// + Float48 = 0x0044, + + /// + /// Indicates the type index references the 64 bit real type. + /// + Float64 = 0x0041, + + /// + /// Indicates the type index references the 80 bit real type. + /// + Float80 = 0x0042, + + /// + /// Indicates the type index references the 128 bit real type. + /// + Float128 = 0x0043, + + /// + /// Indicates the type index references the 16 bit complex type. + /// + Complex16 = 0x0056, + + /// + /// Indicates the type index references the 32 bit complex type. + /// + Complex32 = 0x0050, + + /// + /// Indicates the type index references the 32 bit PP complex type. + /// + Complex32PartialPrecision = 0x0055, + + /// + /// Indicates the type index references the 48 bit complex type. + /// + Complex48 = 0x0054, + + /// + /// Indicates the type index references the 64 bit complex type. + /// + Complex64 = 0x0051, + + /// + /// Indicates the type index references the 80 bit complex type. + /// + Complex80 = 0x0052, + + /// + /// Indicates the type index references the 128 bit complex type. + /// + Complex128 = 0x0053, + + /// + /// Indicates the type index references the 8 bit boolean type. + /// + Boolean8 = 0x0030, + + /// + /// Indicates the type index references the 16 bit boolean type. + /// + Boolean16 = 0x0031, + + /// + /// Indicates the type index references the 32 bit boolean type. + /// + Boolean32 = 0x0032, + + /// + /// Indicates the type index references the 64 bit boolean type. + /// + Boolean64 = 0x0033, + + /// + /// Indicates the type index references the 128 bit boolean type. + /// + Boolean128 = 0x0034, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs new file mode 100644 index 000000000..7dff91423 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs @@ -0,0 +1,50 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible modes that a simple type in a PDB image can be set to. +/// +/// +/// Reference: https://llvm.org/docs/PDB/TpiStream.html +/// +public enum SimpleTypeMode +{ + /// + /// Indicates the type is not a pointer. + /// + Direct = 0, + + /// + /// Indicates the type is a near pointer. + /// + NearPointer = 1, + + /// + /// Indicates the type is a far pointer. + /// + FarPointer = 2, + + /// + /// Indicates the type is a huge pointer. + /// + HugePointer = 3, + + /// + /// Indicates the type is a 32 bit near pointer. + /// + NearPointer32 = 4, + + /// + /// Indicates the type is a 32 bit far pointer. + /// + FarPointer32 = 5, + + /// + /// Indicates the type is a 64 bit near pointer. + /// + NearPointer64 = 6, + + /// + /// Indicates the type is a 128 bit near pointer. + /// + NearPointer128 = 7, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs new file mode 100644 index 000000000..8b36de431 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs @@ -0,0 +1,60 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a simple type referenced by a simple type index. +/// +public class SimpleTypeRecord : CodeViewTypeRecord +{ + /// + /// Constructs a new simple type based on the provided type index. + /// + /// The type index. + public SimpleTypeRecord(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Constructs a new simple type with the provided type kind. + /// + /// The type kind. + public SimpleTypeRecord(SimpleTypeKind kind) + : base((uint) kind) + { + } + + /// + /// Constructs a new simple type with the provided type kind and mode. + /// + /// The type kind. + /// The mode indicating the pointer specifiers added to the type. + public SimpleTypeRecord(SimpleTypeKind kind, SimpleTypeMode mode) + : base((uint) kind | ((uint) mode << 8)) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.SimpleType; + + /// + /// Gets the kind of the simple type. + /// + public SimpleTypeKind Kind => (SimpleTypeKind) (TypeIndex & 0b1111_1111); + + /// + /// Gets the mode describing the pointer specifiers that are added to the simple type. + /// + public SimpleTypeMode Mode => (SimpleTypeMode) ((TypeIndex >> 8) & 0b1111); + + /// + /// Gets a value indicating whether the type is a pointer or not. + /// + public bool IsPointer => Mode != SimpleTypeMode.Direct; + + /// + public override string ToString() => Mode == SimpleTypeMode.Direct + ? Kind.ToString() + : $"{Kind}*"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs new file mode 100644 index 000000000..b06398da4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs @@ -0,0 +1,29 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a static data member in a class or structure type. +/// +public class StaticDataField : CodeViewDataField +{ + /// + /// Initializes an empty static data field. + /// + /// The type index to assign to the field. + protected StaticDataField(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new static data member. + /// + /// The name. + /// The data type of the member. + public StaticDataField(Utf8String name, CodeViewTypeRecord dataType) + : base(dataType, name) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.StMember; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs new file mode 100644 index 000000000..64f582c6b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs @@ -0,0 +1,80 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible attributes that can be assigned to a structure, class or enum type symbol. +/// +[Flags] +public enum StructureAttributes : ushort +{ + /// + /// Indicates the structure is packed. + /// + Packed = 0b00000000_00000001, + + /// + /// Indicates the structure defines constructors or destructors. + /// + Ctor = 0b00000000_00000010, + + /// + /// Indicates the structure defines overloaded operators. + /// + OvlOps = 0b00000000_00000100, + + /// + /// Indicates the structure is a nested class. + /// + IsNested = 0b00000000_00001000, + + /// + /// Indicates the structure defines nested types. + /// + CNested = 0b00000000_00010000, + + /// + /// Indicates the structure defines an overloaded assignment (=) operator. + /// + OpAssign = 0b00000000_00100000, + + /// + /// Indicates the structure defines casting methods. + /// + OpCast = 0b00000000_01000000, + + /// + /// Indicates the structure true is a forward reference. + /// + FwdRef = 0b00000000_10000000, + + /// + /// Indicates the structure is a scoped definition. + /// + Scoped = 0b00000001_00000000, + + /// + /// Indicates the structure has a decorated name following the regular naming conventions. + /// + HasUniqueName = 0b00000010_00000000, + + /// + /// Indicates the structure cannot be used as a base class. + /// + Sealed = 0b00000100_00000000, + + /// + /// Defines the mask for the floating point type that is used within this structure. + /// + HfaMask = 0b00011000_00000000, + + /// + /// Indicates the structure is an intrinsic type. + /// + Intrinsic = 0b00100000_00000000, + + /// + /// Defines the mask for the MoCOM type kind. + /// + MoComMask = 0b11000000_00000000, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs new file mode 100644 index 000000000..a28d73ceb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs @@ -0,0 +1,70 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type of a value that may have several representations or formats within the same position of memory. +/// +public class UnionTypeRecord : CodeViewCompositeTypeRecord +{ + private readonly LazyVariable _uniqueName; + + /// + /// Initializes an empty union type. + /// + /// The type index to assign to the union. + protected UnionTypeRecord(uint typeIndex) + : base(typeIndex) + { + _uniqueName = new LazyVariable(GetUniqueName); + } + + /// + /// Creates a new union type with the provided size. + /// + /// The total size in bytes of the union. + public UnionTypeRecord(ulong size) + : base(0) + { + _uniqueName = new LazyVariable(Utf8String.Empty); + Size = size; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Union; + + /// + /// Gets or sets the total size in bytes of the union type. + /// + public ulong Size + { + get; + set; + } + + /// + /// Gets or sets the uniquely identifiable name for this type. + /// + public Utf8String UniqueName + { + get => _uniqueName.Value; + set => _uniqueName.Value = value; + } + + /// + /// Obtains the uniquely identifiable name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetUniqueName() => null; + + /// + public override string ToString() + { + if (!Utf8String.IsNullOrEmpty(Name)) + return Name; + if (!Utf8String.IsNullOrEmpty(UniqueName)) + return UniqueName; + return $""; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs new file mode 100644 index 000000000..7fd7898f1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs @@ -0,0 +1,47 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents an unknown or unsupported CodeView type record. +/// +public class UnknownCodeViewLeaf : CodeViewLeaf +{ + /// + /// Creates a new unknown type record. + /// + /// The type of symbol. + /// The raw data stored in the record. + public UnknownCodeViewLeaf(CodeViewLeafKind leafKind, byte[] data) + : this(0, leafKind, data) + { + } + + /// + /// Creates a new unknown type record. + /// + /// The type index to assign to the type + /// The type of symbol. + /// The raw data stored in the record. + internal UnknownCodeViewLeaf(uint typeIndex, CodeViewLeafKind leafKind, byte[] data) + : base(typeIndex) + { + LeafKind = leafKind; + Data = data; + } + + /// + public override CodeViewLeafKind LeafKind + { + get; + } + + /// + /// Gets the raw data stored in the record. + /// + public byte[] Data + { + get; + } + + /// + public override string ToString() => $"{LeafKind.ToString()} ({Data.Length.ToString()} bytes)"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs new file mode 100644 index 000000000..99d5edf0e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs @@ -0,0 +1,115 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a direct or indirect reference to a virtual base class object in a structure. +/// +public class VBaseClassField : CodeViewField +{ + private readonly LazyVariable _baseType; + private readonly LazyVariable _basePointerType; + + /// + /// Initializes a new empty virtual base class field. + /// + /// The type index to assign to the field. + protected VBaseClassField(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + _basePointerType = new LazyVariable(GetBasePointerType); + } + + /// + /// Creates a new virtual base class field. + /// + /// The type to reference as base type. + /// The type of the virtual base pointer. + /// The offset of the virtual base pointer + /// The offset from the base table. + /// true if the field is an indirect virtual base class, false otherwise. + public VBaseClassField( + CodeViewTypeRecord baseType, + CodeViewTypeRecord pointerType, + ulong pointerOffset, + ulong tableOffset, + bool isIndirect) + : base(0) + { + _baseType = new LazyVariable(baseType); + _basePointerType = new LazyVariable(pointerType); + PointerOffset = pointerOffset; + TableOffset = tableOffset; + IsIndirect = isIndirect; + } + + /// + /// Gets or sets a value indicating whether the virtual base class is an indirect base class. + /// + public bool IsIndirect + { + get; + set; + } + + /// + public override CodeViewLeafKind LeafKind => IsIndirect + ? CodeViewLeafKind.IVBClass + : CodeViewLeafKind.VBClass; + + /// + /// Gets or sets the base type that this base class is referencing. + /// + public CodeViewTypeRecord? Type + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the type of the base pointer that this base class uses. + /// + public CodeViewTypeRecord? PointerType + { + get => _basePointerType.Value; + set => _basePointerType.Value = value; + } + + /// + /// Gets or sets the virtual base pointer offset relative to the address point. + /// + public ulong PointerOffset + { + get; + set; + } + + /// + /// Gets or sets the virtual base pointer offset relative to the virtual base table. + /// + public ulong TableOffset + { + get; + set; + } + + /// + /// Obtains the base type that the class is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + /// Obtains the type of the base pointer that the class is uses. + /// + /// The base pointer type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBasePointerType() => null; + + /// + public override string ToString() => Type?.ToString() ?? "<<>>"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs new file mode 100644 index 000000000..11355cad4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs @@ -0,0 +1,50 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents the virtual function table field in a class. +/// +public class VTableField : CodeViewField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty virtual function table field. + /// + /// The type index to assign to the type. + protected VTableField(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetPointerType); + } + + /// + /// Creates a new virtual function table field. + /// + /// The pointer type to use. + public VTableField(CodeViewTypeRecord pointerType) + : base(0) + { + _type = new LazyVariable(pointerType); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.VFuncTab; + + /// + /// Gets or sets the pointer type of the virtual function table. + /// + public CodeViewTypeRecord? PointerType + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Obtains the pointer type that the virtual function table type. + /// + /// The pointer type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetPointerType() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs new file mode 100644 index 000000000..abbb9bba9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs @@ -0,0 +1,17 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible types that a single entry in a can be. +/// +public enum VTableShapeEntry : byte +{ +#pragma warning disable CS1591 + Near = 0x00, + Far = 0x01, + Thin = 0x02, + Outer = 0x03, + Meta = 0x04, + Near32 = 0x05, + Far32 = 0x06, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs new file mode 100644 index 000000000..508664c32 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Describes the shape of the virtual function table of a class or structure type. +/// +public class VTableShapeLeaf : CodeViewLeaf +{ + private IList? _entries; + + /// + /// Initializes a new empty virtual function table shape. + /// + /// The type index to assign to the shape. + protected VTableShapeLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty virtual function table shape. + /// + public VTableShapeLeaf() + : base(0) + { + } + + /// + /// Creates a new virtual function table shape with the provided entries. + /// + public VTableShapeLeaf(params VTableShapeEntry[] entries) + : base(0) + { + _entries = new List(entries); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.VTShape; + + /// + /// Gets the list of entries that defines the shape of the virtual function table. + /// + public IList Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the list of entries stored in the shape. + /// + /// The entries. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs new file mode 100644 index 000000000..3fb37697a --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs @@ -0,0 +1,30 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Provides members defining all attributes that can be assigned to a single DBI stream. +/// +[Flags] +public enum DbiAttributes : ushort +{ + /// + /// Indicates no attributes were assigned. + /// + None = 0, + + /// + /// Indicates the program was linked in an incremental manner. + /// + IncrementallyLinked = 1, + + /// + /// Indicates private symbols were stripped from the PDB file. + /// + PrivateSymbolsStripped = 2, + + /// + /// Indicates the program was linked using link.exe with the undocumented /DEBUG:CTYPES flag. + /// + HasConflictingTypes = 4, +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs new file mode 100644 index 000000000..247bd21ac --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -0,0 +1,595 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Represents the DBI Stream (also known as the Debug Information stream). +/// +public class DbiStream : SegmentBase +{ + /// + /// Gets the default fixed MSF stream index for the DBI stream. + /// + public const int StreamIndex = 3; + + private IList? _modules; + private IList? _sectionContributions; + private IList? _sectionMaps; + private readonly LazyVariable _typeServerMapStream; + private readonly LazyVariable _ecStream; + private IList? _sourceFiles; + private IList? _extraStreamIndices; + + /// + /// Creates a new empty DBI stream. + /// + public DbiStream() + { + _typeServerMapStream = new LazyVariable(GetTypeServerMapStream); + _ecStream = new LazyVariable(GetECStream); + IsNewVersionFormat = true; + } + + /// + /// Gets or sets the version signature assigned to the DBI stream. + /// + /// + /// This value should always be -1 for valid PDB files. + /// + public int VersionSignature + { + get; + set; + } = -1; + + /// + /// Gets or sets the version number of the DBI header. + /// + /// + /// Modern tooling only recognize the VC7.0 file format. + /// + public DbiStreamVersion VersionHeader + { + get; + set; + } = DbiStreamVersion.V70; + + /// + /// Gets or sets the number of times the DBI stream has been written. + /// + public uint Age + { + get; + set; + } = 1; + + /// + /// Gets or sets the MSF stream index of the Global Symbol Stream. + /// + public ushort GlobalStreamIndex + { + get; + set; + } + + /// + /// Gets or sets a bitfield containing the major and minor version of the toolchain that was used to build the program. + /// + public ushort BuildNumber + { + get; + set; + } + + /// + /// Gets or sets a value indicating that the DBI stream is using the new file format (NewDBI). + /// + public bool IsNewVersionFormat + { + get => (BuildNumber & 0x8000) != 0; + set => BuildNumber = (ushort) ((BuildNumber & ~0x8000) | (value ? 0x8000 : 0)); + } + + /// + /// Gets or sets the major version of the toolchain that was used to build the program. + /// + public byte BuildMajorVersion + { + get => (byte) ((BuildNumber >> 8) & 0x7F); + set => BuildNumber = (ushort) ((BuildNumber & ~0x7F00) | (value << 8)); + } + + /// + /// Gets or sets the minor version of the toolchain that was used to build the program. + /// + public byte BuildMinorVersion + { + get => (byte) (BuildNumber & 0xFF); + set => BuildNumber = (ushort) ((BuildNumber & ~0x00FF) | value); + } + + /// + /// Gets or sets the MSF stream index of the Public Symbol Stream. + /// + public ushort PublicStreamIndex + { + get; + set; + } + + /// + /// Gets or sets the version number of mspdbXXXX.dll that was used to produce this PDB file. + /// + public ushort PdbDllVersion + { + get; + set; + } + + /// + /// Gets or sets the MSF stream index of the Symbol Record Stream. + /// + public ushort SymbolRecordStreamIndex + { + get; + set; + } + + /// + /// Unknown. + /// + public ushort PdbDllRbld + { + get; + set; + } + + /// + /// Gets or sets the MSF stream index of the MFC type server. + /// + public uint MfcTypeServerIndex + { + get; + set; + } + + /// + /// Gets or sets attributes associated to the DBI stream. + /// + public DbiAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the machine type the program was compiled for. + /// + public MachineType Machine + { + get; + set; + } + + /// + /// Gets a collection of modules (object files) that were linked together into the program. + /// + public IList Modules + { + get + { + if (_modules is null) + Interlocked.CompareExchange(ref _modules, GetModules(), null); + return _modules; + } + } + + /// + /// Gets a collection of section contributions describing the layout of the sections of the final executable file. + /// + public IList SectionContributions + { + get + { + if (_sectionContributions is null) + Interlocked.CompareExchange(ref _sectionContributions, GetSectionContributions(), null); + return _sectionContributions; + } + } + + /// + /// Gets a collection of section mappings stored in the section mapping sub stream. + /// + /// + /// The exact purpose of this is unknown, but it seems to be always containing a copy of the sections in the final + /// executable file. + /// + public IList SectionMaps + { + get + { + if (_sectionMaps is null) + Interlocked.CompareExchange(ref _sectionMaps, GetSectionMaps(), null); + return _sectionMaps; + } + } + + /// + /// Gets or sets the contents of the type server map sub stream. + /// + /// + /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as + /// a raw segment. + /// + public ISegment? TypeServerMapStream + { + get => _typeServerMapStream.Value; + set => _typeServerMapStream.Value = value; + } + + /// + /// Gets or sets the contents of the Edit-and-Continue sub stream. + /// + /// + /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as + /// a raw segment. + /// + public ISegment? ECStream + { + get => _ecStream.Value; + set => _ecStream.Value = value; + } + + /// + /// Gets a collection of source files assigned to each module in . + /// + /// + /// Every collection of source files within this list corresponds to exactly the module within + /// at the same index. For example, the first source file list in this collection is the source file list of the + /// first module. + /// + public IList SourceFiles + { + get + { + if (_sourceFiles is null) + Interlocked.CompareExchange(ref _sourceFiles, GetSourceFiles(), null); + return _sourceFiles; + } + } + + /// + /// Gets a collection of indices referring to additional debug streams in the MSF file. + /// + public IList ExtraStreamIndices + { + get + { + if (_extraStreamIndices is null) + Interlocked.CompareExchange(ref _extraStreamIndices, GetExtraStreamIndices(), null); + return _extraStreamIndices; + } + } + + /// + /// Reads a single DBI stream from the provided input stream. + /// + /// The input stream. + /// The parsed DBI stream. + public static DbiStream FromReader(BinaryStreamReader reader) => new SerializedDbiStream(reader); + + /// + /// Obtains the list of module descriptors. + /// + /// The module descriptors + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetModules() => new List(); + + /// + /// Obtains the list of section contributions. + /// + /// The section contributions. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSectionContributions() => new List(); + + /// + /// Obtains the list of section maps. + /// + /// The section maps. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSectionMaps() => new List(); + + /// + /// Obtains the contents of the type server map sub stream. + /// + /// The contents of the sub stream. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ISegment? GetTypeServerMapStream() => null; + + /// + /// Obtains the contents of the EC sub stream. + /// + /// The contents of the sub stream. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ISegment? GetECStream() => null; + + /// + /// Obtains a table that assigns a list of source files to every module referenced in the PDB file. + /// + /// The table. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSourceFiles() => new List(); + + /// + /// Obtains the list of indices referring to additional debug streams in the MSF file. + /// + /// The list of indices. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetExtraStreamIndices() => new List(); + + /// + public override uint GetPhysicalSize() + { + return GetHeaderSize() + + GetModuleStreamSize() + + GetSectionContributionStreamSize() + + GetSectionMapStreamSize() + + GetSourceInfoStreamSize() + + GetTypeServerMapStreamSize() + + GetECStreamSize() + + GetOptionalDebugStreamSize() + ; + } + + private static uint GetHeaderSize() + { + return sizeof(int) // VersionSignature + + sizeof(DbiStreamVersion) // VersionHeader + + sizeof(uint) // Age + + sizeof(ushort) // GlobalStreamIndex + + sizeof(ushort) // BuildNumber + + sizeof(ushort) // PublicStreamIndex + + sizeof(ushort) // PdbDllVersion + + sizeof(ushort) // SymbolRecordStreamIndex + + sizeof(ushort) // PdbDllRbld + + sizeof(uint) // ModuleInfoSize + + sizeof(uint) // SectionContributionSize + + sizeof(uint) // SectionMapSize + + sizeof(uint) // SourceInfoSize + + sizeof(uint) // TypeServerMapSize + + sizeof(uint) // MfcTypeServerIndex + + sizeof(uint) // OptionalDebugStreamSize + + sizeof(uint) // ECStreamSize + + sizeof(DbiAttributes) // Attributes + + sizeof(MachineType) // MachineType + + sizeof(uint) // Padding + ; + } + + private uint GetModuleStreamSize() + { + return ((uint) Modules.Sum(m => m.GetPhysicalSize())).Align(sizeof(uint)); + } + + private uint GetSectionContributionStreamSize() + { + return sizeof(uint) // version + + SectionContribution.EntrySize * (uint) SectionContributions.Count; + } + + private uint GetSectionMapStreamSize() + { + return sizeof(ushort) // Count + + sizeof(ushort) // LogCount + + SectionMap.EntrySize * (uint) SectionMaps.Count; + } + + private uint GetSourceInfoStreamSize() + { + uint totalFileCount = 0; + uint nameBufferSize = 0; + var stringOffsets = new Dictionary(); + + // Simulate the construction of name buffer + for (int i = 0; i < SourceFiles.Count; i++) + { + var collection = SourceFiles[i]; + totalFileCount += (uint) collection.Count; + + for (int j = 0; j < collection.Count; j++) + { + // If name is not added yet, "append" to the name buffer. + var name = collection[j]; + if (!stringOffsets.ContainsKey(name)) + { + stringOffsets[name] = nameBufferSize; + nameBufferSize += (uint) name.ByteCount + 1u; + } + } + } + + return (sizeof(ushort) // ModuleCount + + sizeof(ushort) // SourceFileCount + + sizeof(ushort) * (uint) SourceFiles.Count // ModuleIndices + + sizeof(ushort) * (uint) SourceFiles.Count // SourceFileCounts + + sizeof(uint) * totalFileCount // NameOffsets + + nameBufferSize // NameBuffer + ).Align(4); + } + + private uint GetTypeServerMapStreamSize() + { + return TypeServerMapStream?.GetPhysicalSize().Align(sizeof(uint)) ?? 0u; + } + + private uint GetOptionalDebugStreamSize() + { + return (uint) (ExtraStreamIndices.Count * sizeof(ushort)); + } + + private uint GetECStreamSize() + { + return ECStream?.GetPhysicalSize() ?? 0u; + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + WriteHeader(writer); + WriteModuleStream(writer); + WriteSectionContributionStream(writer); + WriteSectionMapStream(writer); + WriteSourceInfoStream(writer); + WriteTypeServerMapStream(writer); + WriteECStream(writer); + WriteOptionalDebugStream(writer); + } + + private void WriteHeader(IBinaryStreamWriter writer) + { + writer.WriteInt32(VersionSignature); + writer.WriteUInt32((uint) VersionHeader); + writer.WriteUInt32(Age); + writer.WriteUInt16(GlobalStreamIndex); + writer.WriteUInt16(BuildNumber); + writer.WriteUInt16(PublicStreamIndex); + writer.WriteUInt16(PdbDllVersion); + writer.WriteUInt16(SymbolRecordStreamIndex); + writer.WriteUInt16(PdbDllRbld); + + writer.WriteUInt32(GetModuleStreamSize()); + writer.WriteUInt32(GetSectionContributionStreamSize()); + writer.WriteUInt32(GetSectionMapStreamSize()); + writer.WriteUInt32(GetSourceInfoStreamSize()); + writer.WriteUInt32(GetTypeServerMapStreamSize()); + + writer.WriteUInt32(MfcTypeServerIndex); + + writer.WriteUInt32(GetOptionalDebugStreamSize()); + writer.WriteUInt32(GetECStreamSize()); + + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16((ushort) Machine); + + writer.WriteUInt32(0); + } + + private void WriteModuleStream(IBinaryStreamWriter writer) + { + var modules = Modules; + for (int i = 0; i < modules.Count; i++) + modules[i].Write(writer); + + writer.Align(sizeof(uint)); + } + + private void WriteSectionContributionStream(IBinaryStreamWriter writer) + { + // TODO: make customizable + writer.WriteUInt32((uint) SectionContributionStreamVersion.Ver60); + + var contributions = SectionContributions; + for (int i = 0; i < contributions.Count; i++) + contributions[i].Write(writer); + + writer.Align(sizeof(uint)); + } + + private void WriteSectionMapStream(IBinaryStreamWriter writer) + { + var maps = SectionMaps; + + // Count and LogCount. + writer.WriteUInt16((ushort) maps.Count); + writer.WriteUInt16((ushort) maps.Count); + + // Entries. + for (int i = 0; i < maps.Count; i++) + maps[i].Write(writer); + + writer.Align(sizeof(uint)); + } + + private void WriteSourceInfoStream(IBinaryStreamWriter writer) + { + var sourceFiles = SourceFiles; + int totalFileCount = sourceFiles.Sum(x => x.Count); + + // Module and total file count (truncated to 16 bits) + writer.WriteUInt16((ushort) (sourceFiles.Count & 0xFFFF)); + writer.WriteUInt16((ushort) (totalFileCount & 0xFFFF)); + + // Module indices. Unsure if this is correct, but this array does not seem to be really used by the ref impl. + for (ushort i = 0; i < sourceFiles.Count; i++) + writer.WriteUInt16(i); + + // Source file counts. + for (int i = 0; i < sourceFiles.Count; i++) + writer.WriteUInt16((ushort) sourceFiles[i].Count); + + // Build up string buffer and name offset table. + using var stringBuffer = new MemoryStream(); + var stringWriter = new BinaryStreamWriter(stringBuffer); + var stringOffsets = new Dictionary(); + + for (int i = 0; i < sourceFiles.Count; i++) + { + var collection = sourceFiles[i]; + for (int j = 0; j < collection.Count; j++) + { + // If not present already, append to string buffer. + var name = collection[j]; + if (!stringOffsets.TryGetValue(name, out uint offset)) + { + offset = (uint) stringWriter.Offset; + stringOffsets[name] = offset; + stringWriter.WriteBytes(name.GetBytesUnsafe()); + stringWriter.WriteByte(0); + } + + // Write name offset + writer.WriteUInt32(offset); + } + } + + // Write string buffer. + writer.WriteBytes(stringBuffer.ToArray()); + + writer.Align(sizeof(uint)); + } + + private void WriteTypeServerMapStream(IBinaryStreamWriter writer) + { + TypeServerMapStream?.Write(writer); + writer.Align(sizeof(uint)); + } + + private void WriteOptionalDebugStream(IBinaryStreamWriter writer) + { + var extraIndices = ExtraStreamIndices; + + for (int i = 0; i < extraIndices.Count; i++) + writer.WriteUInt16(extraIndices[i]); + } + + private void WriteECStream(IBinaryStreamWriter writer) => ECStream?.Write(writer); +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs new file mode 100644 index 000000000..fe76de860 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs @@ -0,0 +1,15 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Provides members defining all possible DBI stream format version numbers. +/// +public enum DbiStreamVersion +{ +#pragma warning disable CS1591 + VC41 = 930803, + V50 = 19960307, + V60 = 19970606, + V70 = 19990903, + V110 = 20091201 +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs new file mode 100644 index 000000000..174ec2dab --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs @@ -0,0 +1,207 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Represents a reference to a single module (object file) that was linked into a program. +/// +public class ModuleDescriptor : IWritable +{ + /// + /// Gets or sets a description of the section within the final binary which contains code + /// and/or data from this module. + /// + public SectionContribution SectionContribution + { + get; + set; + } = new(); + + /// + /// Gets or sets the attributes assigned to this module descriptor. + /// + public ModuleDescriptorAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the index of the type server for this module. + /// + public ushort TypeServerIndex + { + get => (ushort) ((ushort) Attributes >> 8); + set => Attributes = (Attributes & ~ModuleDescriptorAttributes.TsmMask) | (ModuleDescriptorAttributes) (value << 8); + } + + /// + /// Gets or sets the MSF stream index of the stream that the symbols of this module. + /// + public ushort SymbolStreamIndex + { + get; + set; + } + + /// + /// Gets or sets the size of the CodeView data within the module's symbol stream. + /// + public uint SymbolDataSize + { + get; + set; + } + + /// + /// Gets or sets the size of the C11-style CodeView data within the module's symbol stream. + /// + public uint SymbolC11DataSize + { + get; + set; + } + + /// + /// Gets or sets the size of the C13-style CodeView data within the module's symbol stream. + /// + public uint SymbolC13DataSize + { + get; + set; + } + + /// + /// Gets or sets the number of source files that contributed to this module during the compilation. + /// + public ushort SourceFileCount + { + get; + set; + } + + /// + /// Gets or sets the offset in the names buffer of the primary translation unit. + /// + /// + /// For most compilers this value is set to zero. + /// + public uint SourceFileNameIndex + { + get; + set; + } + + /// + /// Gets or sets the offset in the names buffer of the PDB file. + /// + /// + /// For most modules (except the special * LINKER * module) this value is set to zero. + /// + public uint PdbFilePathNameIndex + { + get; + set; + } + + /// + /// Gets or sets the name of the module. + /// + /// + /// This is often a full path to the object file that was passed into link.exe directly, or a string in the + /// form of Import:dll_name + /// + public Utf8String? ModuleName + { + get; + set; + } + + /// + /// Gets or sets the name of the object file name. + /// + /// + /// In the case this module is linked directly passed to link.exe, this is the same as . + /// If the module comes from an archive, this is the full path to that archive. + /// + public Utf8String? ObjectFileName + { + get; + set; + } + + /// + /// Parses a single module descriptor from the provided input stream. + /// + /// The input stream. + /// THe parsed module descriptor. + public static ModuleDescriptor FromReader(ref BinaryStreamReader reader) + { + var result = new ModuleDescriptor(); + + reader.ReadUInt32(); + result.SectionContribution = SectionContribution.FromReader(ref reader); + result.Attributes = (ModuleDescriptorAttributes) reader.ReadUInt16(); + result.SymbolStreamIndex = reader.ReadUInt16(); + result.SymbolDataSize = reader.ReadUInt32(); + result.SymbolC11DataSize = reader.ReadUInt32(); + result.SymbolC13DataSize = reader.ReadUInt32(); + result.SourceFileCount = reader.ReadUInt16(); + reader.ReadUInt16(); + reader.ReadUInt32(); + result.SourceFileNameIndex = reader.ReadUInt32(); + result.PdbFilePathNameIndex = reader.ReadUInt32(); + result.ModuleName = reader.ReadUtf8String(); + result.ObjectFileName = reader.ReadUtf8String(); + reader.Align(4); + + return result; + } + + /// + public uint GetPhysicalSize() + { + return (sizeof(uint) // Unused1 + + SectionContribution.GetPhysicalSize() // SectionContribution + + sizeof(ModuleDescriptorAttributes) // Attributes + + sizeof(ushort) // SymbolStreamIndex + + sizeof(uint) // SymbolDataSize + + sizeof(uint) // SymbolC11DataSize + + sizeof(uint) // SymbolC13DataSize + + sizeof(ushort) // SourceFileCount + + sizeof(ushort) // Padding + + sizeof(uint) // Unused2 + + sizeof(uint) // SourceFileNameIndex + + sizeof(uint) // PdbFilePathNameIndex + + (uint) (ModuleName?.ByteCount ?? 0) + 1 // ModuleName + + (uint) (ObjectFileName?.ByteCount ?? 0) + 1 // ObjectFileName + ).Align(4); + } + + /// + public void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt32(0); + SectionContribution.Write(writer); + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16(SymbolStreamIndex); + writer.WriteUInt32(SymbolDataSize); + writer.WriteUInt32(SymbolC11DataSize); + writer.WriteUInt32(SymbolC13DataSize); + writer.WriteUInt16(SourceFileCount); + writer.WriteUInt16(0); + writer.WriteUInt32(0); + writer.WriteUInt32(SourceFileNameIndex); + writer.WriteUInt32(PdbFilePathNameIndex); + if (ModuleName is not null) + writer.WriteBytes(ModuleName.GetBytesUnsafe()); + writer.WriteByte(0); + if (ObjectFileName is not null) + writer.WriteBytes(ObjectFileName.GetBytesUnsafe()); + writer.WriteByte(0); + writer.Align(4); + } + + /// + public override string ToString() => ModuleName ?? ObjectFileName ?? "?"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs new file mode 100644 index 000000000..5b6a018c0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs @@ -0,0 +1,25 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Defines all possible flags that can be assigned to a module descriptor. +/// +[Flags] +public enum ModuleDescriptorAttributes : ushort +{ + /// + /// Indicates the module has been written to since reading the PDB. + /// + Dirty = 1, + + /// + /// Indicates the module contains Edit & Continue information. + /// + EC = 2, + + /// + /// Provides a mask for the type server index that is stored within the flags. + /// + TsmMask = 0xFF00, +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs new file mode 100644 index 000000000..83daaae3e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs @@ -0,0 +1,128 @@ +using System.Text; +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Describes the section in the final executable file that a particular object or module is stored at. +/// +public class SectionContribution : IWritable +{ + /// + /// The total size in bytes of a single on the disk. + /// + public const int EntrySize = + sizeof(ushort) // Section + + sizeof(ushort) // Padding1 + + sizeof(uint) // Offset + + sizeof(uint) // Size + + sizeof(uint) // Characteristics + + sizeof(ushort) // ModuleIndex + + sizeof(ushort) // Padding2 + + sizeof(uint) // DataCrc + + sizeof(uint) // RelocCrc + ; + + /// + /// Gets or sets the index of the section. + /// + public ushort Section + { + get; + set; + } + + /// + /// Gets or sets the offset within the section that this contribution starts at. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets the size of the section contribution. + /// + public uint Size + { + get; + set; + } + + /// + /// Gets or sets the section flags and permissions associated to this section contribution. + /// + public SectionFlags Characteristics + { + get; + set; + } + + /// + /// Gets or sets the index of the module. + /// + public ushort ModuleIndex + { + get; + set; + } + + /// + /// Gets or sets a cyclic redundancy code that can be used to verify the data section of this contribution. + /// + public uint DataCrc + { + get; + set; + } + + /// + /// Gets or sets a cyclic redundancy code that can be used to verify the relocation section of this contribution. + /// + public uint RelocCrc + { + get; + set; + } + + /// + /// Parses a single section contribution from the provided input stream. + /// + /// The input stream. + /// The parsed section contribution. + public static SectionContribution FromReader(ref BinaryStreamReader reader) + { + var result = new SectionContribution(); + + result.Section = reader.ReadUInt16(); + reader.ReadUInt16(); + result.Offset = reader.ReadUInt32(); + result.Size = reader.ReadUInt32(); + result.Characteristics = (SectionFlags) reader.ReadUInt32(); + result.ModuleIndex = reader.ReadUInt16(); + reader.ReadUInt16(); + result.DataCrc = reader.ReadUInt32(); + result.RelocCrc = reader.ReadUInt32(); + + return result; + } + + /// + public uint GetPhysicalSize() => EntrySize; + + /// + public void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt16(Section); + writer.WriteUInt16(0); + writer.WriteUInt32(Offset); + writer.WriteUInt32(Size); + writer.WriteUInt32((uint) Characteristics); + writer.WriteUInt16(ModuleIndex); + writer.WriteUInt16(0); + writer.WriteUInt32(DataCrc); + writer.WriteUInt32(RelocCrc); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs new file mode 100644 index 000000000..48daaf3a4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs @@ -0,0 +1,17 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Provides members defining all valid versions of the Section Contribution sub stream's file format. +/// +public enum SectionContributionStreamVersion : uint +{ + /// + /// Indicates version 6.0 is used. + /// + Ver60 = 0xeffe0000 + 19970605, + + /// + /// Indicates version 2.0 is used. + /// + V2 = 0xeffe0000 + 20140516 +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs new file mode 100644 index 000000000..bf43a7e56 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs @@ -0,0 +1,131 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Represents a single entry in the Section Map sub stream of the DBI stream. +/// +public class SectionMap : IWritable +{ + /// + /// The total size in bytes of a single on the disk. + /// + public const int EntrySize = + sizeof(ushort) // Attributes + + sizeof(ushort) // Ovl + + sizeof(ushort) // Group + + sizeof(ushort) // Frame + + sizeof(ushort) // SectionName + + sizeof(ushort) // ClassName + + sizeof(uint) // Offset + + sizeof(uint) // SectionLength + ; + + /// + /// Gets or sets the attributes assigned to this section map. + /// + public SectionMapAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the logical overlay number of this section map. + /// + public ushort LogicalOverlayNumber + { + get; + set; + } + + /// + /// Gets or sets the group index into the descriptor array. + /// + public ushort Group + { + get; + set; + } + + /// + /// Gets or sets the frame index. + /// + public ushort Frame + { + get; + set; + } + + /// + /// Gets or sets the byte offset of the segment or group name in string table, or 0xFFFF if no name was assigned. + /// + public ushort SectionName + { + get; + set; + } + + /// + /// Gets or sets the byte offset of the class in the string table, or 0xFFFF if no name was assigned.. + /// + public ushort ClassName + { + get; + set; + } + + /// + /// Gets or sets the byte offset of the logical segment within physical segment. If group is set in flags, this is the offset of the group. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets the number of bytes that the segment or group consists of. + /// + public uint SectionLength + { + get; + set; + } + + /// + /// Parses a single section map from the provided input stream. + /// + /// The input stream. + /// The parsed section map. + public static SectionMap FromReader(ref BinaryStreamReader reader) + { + return new SectionMap + { + Attributes = (SectionMapAttributes) reader.ReadUInt16(), + LogicalOverlayNumber = reader.ReadUInt16(), + Group = reader.ReadUInt16(), + Frame = reader.ReadUInt16(), + SectionName = reader.ReadUInt16(), + ClassName = reader.ReadUInt16(), + Offset = reader.ReadUInt32(), + SectionLength = reader.ReadUInt32() + }; + } + + /// + public uint GetPhysicalSize() => EntrySize; + + /// + public void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16(LogicalOverlayNumber); + writer.WriteUInt16(Group); + writer.WriteUInt16(Frame); + writer.WriteUInt16(SectionName); + writer.WriteUInt16(ClassName); + writer.WriteUInt32(Offset); + writer.WriteUInt32(SectionLength); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs new file mode 100644 index 000000000..7f6449772 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs @@ -0,0 +1,42 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Provides members describing all possible attributes that can be assigned to a single section map entry. +/// +public enum SectionMapAttributes : ushort +{ + /// + /// Indicates the segment is readable. + /// + Read = 1 << 0, + + /// + /// Indicates the segment is writable. + /// + Write = 1 << 1, + + /// + /// Indicates the segment is executable. + /// + Execute = 1 << 2, + + /// + /// Indicates the descriptor describes a 32-bit linear address. + /// + AddressIs32Bit = 1 << 3, + + /// + /// Indicates the frame represents a selector. + /// + IsSelector = 1 << 8, + + /// + /// Indicates the frame represents an absolute address. + /// + IsAbsoluteAddress = 1 << 9, + + /// + /// Indicates the descriptor represents a group. + /// + IsGroup = 1 << 10 +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs new file mode 100644 index 000000000..962f02fc7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Implements a DBI stream that pulls its data from an input stream. +/// +public class SerializedDbiStream : DbiStream +{ + private readonly BinaryStreamReader _moduleInfoReader; + private readonly BinaryStreamReader _sectionContributionReader; + private readonly BinaryStreamReader _sectionMapReader; + private readonly BinaryStreamReader _sourceInfoReader; + private readonly BinaryStreamReader _typeServerMapReader; + private readonly BinaryStreamReader _optionalDebugHeaderReader; + private readonly BinaryStreamReader _ecReader; + + /// + /// Parses a DBI stream from an input stream reader. + /// + /// The input stream. + public SerializedDbiStream(BinaryStreamReader reader) + { + VersionSignature = reader.ReadInt32(); + VersionHeader = (DbiStreamVersion) reader.ReadUInt32(); + Age = reader.ReadUInt32(); + GlobalStreamIndex = reader.ReadUInt16(); + BuildNumber = reader.ReadUInt16(); + if (!IsNewVersionFormat) + throw new NotSupportedException("The DBI stream uses the legacy file format, which is not supported."); + + PublicStreamIndex = reader.ReadUInt16(); + PdbDllVersion = reader.ReadUInt16(); + SymbolRecordStreamIndex = reader.ReadUInt16(); + PdbDllRbld = reader.ReadUInt16(); + + uint moduleInfoSize = reader.ReadUInt32(); + uint sectionContributionSize = reader.ReadUInt32(); + uint sectionMapSize = reader.ReadUInt32(); + uint sourceInfoSize = reader.ReadUInt32(); + uint typeServerMapSize = reader.ReadUInt32(); + + MfcTypeServerIndex = reader.ReadUInt32(); + + uint optionalDebugHeaderSize = reader.ReadUInt32(); + uint ecSize = reader.ReadUInt32(); + + Attributes = (DbiAttributes) reader.ReadUInt16(); + Machine = (MachineType) reader.ReadUInt16(); + + _ = reader.ReadUInt32(); + + _moduleInfoReader = reader.ForkRelative(reader.RelativeOffset, moduleInfoSize); + reader.Offset += moduleInfoSize; + _sectionContributionReader = reader.ForkRelative(reader.RelativeOffset, sectionContributionSize); + reader.Offset += sectionContributionSize; + _sectionMapReader = reader.ForkRelative(reader.RelativeOffset, sectionMapSize); + reader.Offset += sectionMapSize; + _sourceInfoReader = reader.ForkRelative(reader.RelativeOffset, sourceInfoSize); + reader.Offset += sourceInfoSize; + _typeServerMapReader = reader.ForkRelative(reader.RelativeOffset, typeServerMapSize); + reader.Offset += typeServerMapSize; + _ecReader = reader.ForkRelative(reader.RelativeOffset, ecSize); + reader.Offset += ecSize; + _optionalDebugHeaderReader = reader.ForkRelative(reader.RelativeOffset, optionalDebugHeaderSize); + reader.Offset += optionalDebugHeaderSize; + } + + /// + protected override IList GetModules() + { + var result = new List(); + + var reader = _moduleInfoReader.Fork(); + while (reader.CanRead(1)) + result.Add(ModuleDescriptor.FromReader(ref reader)); + + return result; + } + + /// + protected override IList GetSectionContributions() + { + var result = new List(); + + var reader = _sectionContributionReader.Fork(); + var version = (SectionContributionStreamVersion) reader.ReadUInt32(); + + while (reader.CanRead(1)) + { + result.Add(SectionContribution.FromReader(ref reader)); + if (version == SectionContributionStreamVersion.V2) + reader.ReadUInt32(); + } + + return result; + } + + /// + protected override IList GetSectionMaps() + { + var reader = _sectionMapReader.Fork(); + + ushort count = reader.ReadUInt16(); + ushort logCount = reader.ReadUInt16(); + + var result = new List(count); + for (int i = 0; i < count; i++) + result.Add(SectionMap.FromReader(ref reader)); + + return result; + } + + /// + protected override ISegment? GetTypeServerMapStream() + { + var reader = _typeServerMapReader.Fork(); + return reader.Length != 0 + ? reader.ReadSegment(reader.Length) + : null; + } + + /// + protected override ISegment? GetECStream() + { + var reader = _ecReader.Fork(); + return reader.Length != 0 + ? reader.ReadSegment(reader.Length) + : null; + } + + /// + protected override IList GetSourceFiles() + { + var reader = _sourceInfoReader.Fork(); + + ushort moduleCount = reader.ReadUInt16(); + ushort sourceFileCount = reader.ReadUInt16(); + + // Read module indices. + ushort[] moduleIndices = new ushort[moduleCount]; + for (int i = 0; i < moduleCount; i++) + moduleIndices[i] = reader.ReadUInt16(); + + // Read module source file counts. + int actualFileCount = 0; + ushort[] moduleFileCounts = new ushort[moduleCount]; + for (int i = 0; i < moduleCount; i++) + { + ushort count = reader.ReadUInt16(); + moduleFileCounts[i] = count; + actualFileCount += count; + } + + // Scope on the name buffer. + var stringReaderBuffer = reader.ForkRelative((uint) (reader.RelativeOffset + actualFileCount * sizeof(uint))); + + // Construct source file lists. + var result = new List(moduleCount); + for (int i = 0; i < moduleCount; i++) + { + var files = new SourceFileCollection(moduleIndices[i]); + ushort fileCount = moduleFileCounts[i]; + + // Read all file paths for this module. + for (int j = 0; j < fileCount; j++) + { + uint nameOffset = reader.ReadUInt32(); + var nameReader = stringReaderBuffer.ForkRelative(nameOffset); + files.Add(nameReader.ReadUtf8String()); + } + + result.Add(files); + } + + return result; + } + + /// + protected override IList GetExtraStreamIndices() + { + var reader = _optionalDebugHeaderReader.Fork(); + + var result = new List((int) (reader.Length / sizeof(ushort))); + + while (reader.CanRead(sizeof(ushort))) + result.Add(reader.ReadUInt16()); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs new file mode 100644 index 000000000..b7ef673e6 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Represents a collection of file paths used as input source code for a single module. +/// +public class SourceFileCollection : List +{ + /// + /// Creates a new empty source file collection. + /// + public SourceFileCollection() + { + } + + /// + /// Creates a new empty source file collection. + /// + /// The original module index for which this collection was compiled. + public SourceFileCollection(uint originalModuleIndex) + { + OriginalModuleIndex = originalModuleIndex; + } + + /// + /// Gets the original module index for which this collection was compiled (if available). + /// + /// + /// The exact purpose of this number is unclear, as this number cannot be reliably used as an index within the + /// DBI stream's module list. Use the index of this list within instead. + /// + public uint OriginalModuleIndex + { + get; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs new file mode 100644 index 000000000..eae02174c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Represents the PDB Info Stream (also known as the PDB stream) +/// +public class InfoStream : SegmentBase +{ + /// + /// Gets the default fixed MSF stream index for the PDB Info stream. + /// + public const int StreamIndex = 1; + + private const int HeaderSize = + sizeof(InfoStreamVersion) // Version + + sizeof(uint) // Signature + + sizeof(uint) // Aage + + 16 //UniqueId + + sizeof(uint) // NameBufferSize + ; + + private IDictionary? _streamIndices; + private IList? _features; + + /// + /// Gets or sets the version of the file format of the PDB info stream. + /// + /// + /// Modern tooling only recognize the VC7.0 file format. + /// + public InfoStreamVersion Version + { + get; + set; + } = InfoStreamVersion.VC70; + + /// + /// Gets or sets the 32-bit UNIX time-stamp of the PDB file. + /// + public uint Signature + { + get; + set; + } + + /// + /// Gets or sets the number of times the PDB file has been written. + /// + public uint Age + { + get; + set; + } = 1; + + /// + /// Gets or sets the unique identifier assigned to the PDB file. + /// + public Guid UniqueId + { + get; + set; + } + + /// + /// Gets a mapping from stream names to their respective stream index within the underlying MSF file. + /// + public IDictionary StreamIndices + { + get + { + if (_streamIndices is null) + Interlocked.CompareExchange(ref _streamIndices, GetStreamIndices(), null); + return _streamIndices; + } + } + + /// + /// Gets a list of characteristics that this PDB has. + /// + public IList Features + { + get + { + if (_features is null) + Interlocked.CompareExchange(ref _features, GetFeatures(), null); + return _features; + } + } + + /// + /// Reads a single PDB info stream from the provided input stream. + /// + /// The input stream. + /// The parsed info stream. + public static InfoStream FromReader(BinaryStreamReader reader) => new SerializedInfoStream(reader); + + /// + /// Obtains the stream name to index mapping of the PDB file. + /// + /// The mapping. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IDictionary GetStreamIndices() => new Dictionary(); + + /// + /// Obtains the features of the PDB file. + /// + /// The features. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetFeatures() => new List + { + PdbFeature.VC140 + }; + + /// + public override uint GetPhysicalSize() + { + uint totalSize = HeaderSize; + + // Name buffer + foreach (var entry in StreamIndices) + totalSize += (uint) entry.Key.ByteCount + 1u; + + // Stream indices hash table. + totalSize += StreamIndices.GetPdbHashTableSize(ComputeStringHash); + + // Last NI + totalSize += sizeof(uint); + + // Feature codes. + totalSize += (uint) Features.Count * sizeof(PdbFeature); + + return totalSize; + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + // Write basic info stream header. + writer.WriteUInt32((uint) Version); + writer.WriteUInt32(Signature); + writer.WriteUInt32(Age); + writer.WriteBytes(UniqueId.ToByteArray()); + + // Construct name buffer, keeping track of the offsets of every name. + using var nameBuffer = new MemoryStream(); + var nameWriter = new BinaryStreamWriter(nameBuffer); + + var stringOffsets = new Dictionary(); + foreach (var entry in StreamIndices) + { + uint offset = (uint) nameWriter.Offset; + nameWriter.WriteBytes(entry.Key.GetBytesUnsafe()); + nameWriter.WriteByte(0); + stringOffsets.Add(entry.Key, offset); + } + + writer.WriteUInt32((uint) nameBuffer.Length); + writer.WriteBytes(nameBuffer.ToArray()); + + // Write the hash table. + StreamIndices.WriteAsPdbHashTable(writer, + ComputeStringHash, + (key, value) => (stringOffsets[key], (uint) value)); + + // last NI, safe to put always zero. + writer.WriteUInt32(0); + + // Write feature codes. + var features = Features; + for (int i = 0; i < features.Count; i++) + writer.WriteUInt32((uint) features[i]); + } + + private static uint ComputeStringHash(Utf8String str) + { + // Note: The hash of a single entry is **deliberately** truncated to a 16 bit number. This is because + // the reference implementation of the name table returns a number of type HASH, which is a typedef + // for "unsigned short". If we don't do this, this will result in wrong buckets being filled in the + // hash table, and thus the serialization would fail. See NMTNI::hash() in Microsoft/microsoft-pdb. + + return (ushort) PdbHash.ComputeV1(str); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs new file mode 100644 index 000000000..d377f5ba1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs @@ -0,0 +1,20 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Provides members defining all possible stream file format versions that PDB defines. +/// +public enum InfoStreamVersion +{ +#pragma warning disable CS1591 + VC2 = 19941610, + VC4 = 19950623, + VC41 = 19950814, + VC50 = 19960307, + VC98 = 19970604, + VC70Dep = 19990604, + VC70 = 20000404, + VC80 = 20030901, + VC110 = 20091201, + VC140 = 20140508, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs new file mode 100644 index 000000000..16e5d3a66 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs @@ -0,0 +1,28 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Provides members defining all possible features that a PDB can have. +/// +public enum PdbFeature : uint +{ + /// + /// Indicates no other feature flags are present, and that an IPI stream is present. + /// + VC110 = 20091201, + + /// + /// Indicates that other feature flags may be present, and that an IPI stream is present. + /// + VC140 = 20140508, + + /// + /// Indicates types can be duplicated in the TPI stream. + /// + NoTypeMerge = 0x4D544F4E, + + /// + /// Indicates the program was linked with /DEBUG:FASTLINK, and all type information is contained in the original + /// object files instead of TPI and IPI streams. + /// + MinimalDebugInfo = 0x494E494D, +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs new file mode 100644 index 000000000..f448ce67f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Implements an PDB info stream that pulls its data from an input stream. +/// +public class SerializedInfoStream : InfoStream +{ + private readonly BinaryStreamReader _reader; + private ulong _featureOffset; + + /// + /// Parses a PDB info stream from an input stream reader. + /// + /// The input stream. + public SerializedInfoStream(BinaryStreamReader reader) + { + Version = (InfoStreamVersion) reader.ReadUInt32(); + Signature = reader.ReadUInt32(); + Age = reader.ReadUInt32(); + + byte[] guidBytes = new byte[16]; + reader.ReadBytes(guidBytes, 0, guidBytes.Length); + + UniqueId = new Guid(guidBytes); + + _reader = reader; + } + + /// + protected override IDictionary GetStreamIndices() + { + var reader = _reader.Fork(); + uint length = reader.ReadUInt32(); + + var stringsReader = reader.ForkRelative(reader.RelativeOffset, length); + var hashTableReader = reader.ForkRelative(reader.RelativeOffset + length); + + var result = PdbHashTable.FromReader(ref hashTableReader, (key, value) => + { + var stringReader = stringsReader.ForkRelative(key); + var keyString = stringReader.ReadUtf8String(); + return (keyString, (int) value); + }); + + hashTableReader.ReadUInt32(); // lastNi (unused). + + _featureOffset = hashTableReader.Offset; + return result; + } + + /// + protected override IList GetFeatures() + { + // We need to read the stream name->index mapping to be able to read the features list of the PDB. + _ = StreamIndices; + + var result = new List(); + + var reader = _reader.ForkAbsolute(_featureOffset); + while (reader.CanRead(sizeof(uint))) + result.Add((PdbFeature) reader.ReadUInt32()); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs new file mode 100644 index 000000000..ed5a6a03b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs @@ -0,0 +1,49 @@ +namespace AsmResolver.Symbols.Pdb.Metadata; + +/// +/// Provides methods for computing hash codes for a PDB hash table. +/// +public static class PdbHash +{ + /// + /// Computes the V1 hash code for a UTF-8 string. + /// + /// The string to compute the hash for. + /// The hash code. + /// + /// See PDB/include/misc.h for reference implementation. + /// + public static unsafe uint ComputeV1(Utf8String value) + { + uint result = 0; + + uint count = (uint) value.ByteCount; + + fixed (byte* ptr = value.GetBytesUnsafe()) + { + byte* p = ptr; + + while (count >= 4) + { + result ^= *(uint*) p; + count -= 4; + p += 4; + } + + if (count >= 2) + { + result ^= *(ushort*) p; + count -= 2; + p += 2; + } + + if (count == 1) + result ^= *p; + } + + result |= 0x20202020; + result ^= result >> 11; + + return result ^ (result >> 16); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs new file mode 100644 index 000000000..fb58f25be --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata; + +/// +/// Provides methods for serializing and deserializing dictionaries as PDB hash tables. +/// +public static class PdbHashTable +{ + // Reference implementation from PDB/include/map.h + // Specifically, Map::load, Map::find and Map::save. + + /// + /// Reads a single PDB hash table from the input stream and converts it into a dictionary. + /// + /// The input stream to read from. + /// A function that maps the raw key-value pairs into high level constructs. + /// The type of keys in the final dictionary. + /// The type of values in the final dictionary. + /// The reconstructed dictionary. + public static Dictionary FromReader( + ref BinaryStreamReader reader, + Func mapper) + where TKey : notnull + { + uint count = reader.ReadUInt32(); + reader.ReadUInt32(); // Capacity + + uint presentWordCount = reader.ReadUInt32(); + reader.RelativeOffset += presentWordCount * sizeof(uint); + + uint deletedWordCount = reader.ReadUInt32(); + reader.RelativeOffset += deletedWordCount * sizeof(uint); + + var result = new Dictionary((int) count); + for (int i = 0; i < count; i++) + { + (uint rawKey, uint rawValue) = (reader.ReadUInt32(), reader.ReadUInt32()); + var (key, value) = mapper(rawKey, rawValue); + result.Add(key, value); + } + + return result; + } + + /// + /// Computes the number of bytes required to store the provided dictionary as a PDB hash table. + /// + /// The dictionary to serialize. + /// A function that computes the hash code for a single key within the dictionary. + /// The type of keys in the input dictionary. + /// The type of values in the input dictionary. + /// The number of bytes required. + public static uint GetPdbHashTableSize( + this IDictionary dictionary, + Func hasher) + where TKey : notnull + { + var info = dictionary.ToPdbHashTable(hasher, null); + + return sizeof(uint) // Count + + sizeof(uint) // Capacity + + sizeof(uint) // Present bitvector word count + + info.PresentWordCount * sizeof(uint) // Present bitvector words + + sizeof(uint) // Deleted bitvector word count (== 0) + + (sizeof(uint) + sizeof(uint)) * (uint) dictionary.Count + ; + } + + /// + /// Serializes a dictionary to a PDB hash table to an output stream. + /// + /// The dictionary to serialize. + /// The output stream to write to. + /// A function that computes the hash code for a single key within the dictionary. + /// A function that maps every key-value pair to raw key-value uint32 pairs. + /// The type of keys in the input dictionary. + /// The type of values in the input dictionary. + public static void WriteAsPdbHashTable( + this IDictionary dictionary, + IBinaryStreamWriter writer, + Func hasher, + Func mapper) + where TKey : notnull + { + var hashTable = dictionary.ToPdbHashTable(hasher, mapper); + + // Write count and capacity. + writer.WriteInt32(dictionary.Count); + writer.WriteUInt32(hashTable.Capacity); + + // Determine which words in the present bitvector to write. + uint wordCount = (hashTable.Capacity + sizeof(uint) - 1) / sizeof(uint); + uint[] words = new uint[wordCount]; + hashTable.Present.CopyTo(words, 0); + while (wordCount > 0 && words[wordCount - 1] == 0) + wordCount--; + + // Write the present bitvector. + writer.WriteUInt32(wordCount); + for (int i = 0; i < wordCount; i++) + writer.WriteUInt32(words[i]); + + // Write deleted bitvector. We just always do 0 (i.e. no deleted buckets). + writer.WriteUInt32(0); + + // Write all buckets. + for (int i = 0; i < hashTable.Keys!.Length; i++) + { + if (hashTable.Present.Get(i)) + { + writer.WriteUInt32(hashTable.Keys![i]); + writer.WriteUInt32(hashTable.Values![i]); + } + } + } + + private static HashTableInfo ToPdbHashTable( + this IDictionary dictionary, + Func hasher, + Func? mapper) + where TKey : notnull + { + uint capacity = ComputeRequiredCapacity(dictionary.Count); + + // Avoid allocating buckets if we actually don't need to (e.g. if we're simply measuring the total size). + uint[]? keys; + uint[]? values; + + if (mapper is null) + { + keys = null; + values = null; + } + else + { + keys = new uint[capacity]; + values = new uint[capacity]; + } + + var present = new BitArray((int) capacity, false); + + // Fill in buckets. + foreach (var item in dictionary) + { + // Find empty bucket to place key-value pair in. + uint hash = hasher(item.Key); + uint index = hash % capacity; + while (present.Get((int) index)) + index = (index + 1) % capacity; + + // Mark bucket as used. + present.Set((int) index, true); + + // Store key-value pair. + if (mapper is not null) + { + (uint key, uint value) = mapper(item.Key, item.Value); + keys![index] = key; + values![index] = value; + } + } + + // Determine final word count in present bit vector. + uint wordCount = (capacity + sizeof(uint) - 1) / sizeof(uint); + uint[] words = new uint[wordCount]; + present.CopyTo(words, 0); + while (wordCount > 0 && words[wordCount - 1] == 0) + wordCount--; + + return new HashTableInfo(capacity, keys, values, present, wordCount); + } + + private static uint ComputeRequiredCapacity(int totalItemCount) + { + // "Simulate" adding all items to the hash table, effectively calculating the capacity of the map. + // TODO: This can probably be calculated with a single formula instead. + + uint capacity = 1; + for (int i = 0; i <= totalItemCount; i++) + { + // Reference implementation allows only 67% of the capacity to be used. + uint maxLoad = capacity * 2 / 3 + 1; + if (i >= maxLoad) + capacity = 2 * maxLoad; + } + + return capacity; + } + + private readonly struct HashTableInfo + { + public readonly uint Capacity; + public readonly uint[]? Keys; + public readonly uint[]? Values; + public readonly BitArray Present; + public readonly uint PresentWordCount; + + public HashTableInfo(uint capacity, uint[]? keys, uint[]? values, BitArray present, uint presentWordCount) + { + Capacity = capacity; + Keys = keys; + Values = values; + Present = present; + PresentWordCount = presentWordCount; + } + } + +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs new file mode 100644 index 000000000..3a4c144c7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedTpiStream : TpiStream +{ + private readonly BinaryStreamReader _recordsReader; + private List<(uint Offset, uint Length)>? _recordOffsets; + + /// + /// Reads a TPI stream from the provided input stream. + /// + /// The input stream to read from. + public SerializedTpiStream(BinaryStreamReader reader) + { + Version = (TpiStreamVersion) reader.ReadUInt32(); + if (Version != TpiStreamVersion.V80) + throw new NotSupportedException($"Unsupported TPI file format version {Version}."); + + uint headerSize = reader.ReadUInt32(); + if (headerSize != TpiStreamHeaderSize) + throw new NotSupportedException("Invalid TPI header size."); + + TypeIndexBegin = reader.ReadUInt32(); + TypeIndexEnd = reader.ReadUInt32(); + TypeRecordsByteCount = reader.ReadUInt32(); + HashStreamIndex = reader.ReadUInt16(); + HashAuxStreamIndex = reader.ReadUInt16(); + HashKeySize = reader.ReadUInt32(); + HashBucketCount = reader.ReadUInt32(); + HashValueBufferOffset = reader.ReadUInt32(); + HashValueBufferLength = reader.ReadUInt32(); + IndexOffsetBufferOffset = reader.ReadUInt32(); + IndexOffsetBufferLength = reader.ReadUInt32(); + HashAdjBufferOffset = reader.ReadUInt32(); + HashAdjBufferLength = reader.ReadUInt32(); + + _recordsReader = reader.ForkRelative(reader.RelativeOffset, TypeRecordsByteCount); + } + + [MemberNotNull(nameof(_recordOffsets))] + private void EnsureRecordOffsetMappingInitialized() + { + if (_recordOffsets is null) + Interlocked.CompareExchange(ref _recordOffsets, GetRecordOffsets(), null); + } + + private List<(uint Offset, uint Length)> GetRecordOffsets() + { + int count = (int) (TypeIndexEnd - TypeIndexBegin); + var result = new List<(uint Offset, uint Length)>(count); + + var reader = _recordsReader.Fork(); + while (reader.CanRead(sizeof(ushort) * 2)) + { + uint offset = reader.RelativeOffset; + ushort length = reader.ReadUInt16(); + result.Add((offset, length)); + reader.Offset += length; + } + + return result; + } + + /// + public override bool TryGetLeafRecordReader(uint typeIndex, out BinaryStreamReader reader) + { + EnsureRecordOffsetMappingInitialized(); + + typeIndex -= TypeIndexBegin; + if (typeIndex >= _recordOffsets.Count) + { + reader = default; + return false; + } + + (uint offset, uint length) = _recordOffsets[(int) typeIndex]; + reader = _recordsReader.ForkRelative(offset, length + sizeof(ushort)); + return true; + } + + /// + protected override void WriteTypeRecords(IBinaryStreamWriter writer) + { + _recordsReader.Fork().WriteToOutput(writer); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs new file mode 100644 index 000000000..f3a5440e3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs @@ -0,0 +1,233 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +/// +/// Represents the Type Information (TPI) stream in a PDB file. +/// +public abstract class TpiStream : SegmentBase +{ + /// + /// Gets the default fixed MSF stream index for the TPI stream. + /// + public const int StreamIndex = 2; + + internal const uint TpiStreamHeaderSize = + sizeof(TpiStreamVersion) // Version + + sizeof(uint) // HeaderSize + + sizeof(uint) // TypeIndexBegin + + sizeof(uint) // TypeIndexEnd + + sizeof(uint) // TypeRecordBytes + + sizeof(ushort) // HashStreamIndex + + sizeof(ushort) // HashAuxStreamIndex + + sizeof(uint) // HashKeySize + + sizeof(uint) // NumHashBuckets + + sizeof(uint) // HashValueBufferOffset + + sizeof(uint) // HashValueBufferLength + + sizeof(uint) // IndexOffsetBufferOffset + + sizeof(uint) // IndexOffsetBufferLength + + sizeof(uint) // HashAdjBufferOffset + + sizeof(uint) // HashAdjBufferLength + ; + + /// + /// Gets or sets the version of the file format that the TPI stream is using. + /// + public TpiStreamVersion Version + { + get; + set; + } = TpiStreamVersion.V80; + + /// + /// Gets or sets the index of the first type record in the stream. + /// + public uint TypeIndexBegin + { + get; + set; + } = 0x1000; + + /// + /// Gets or sets the index of the last type record in the stream. + /// + public uint TypeIndexEnd + { + get; + set; + } + + /// + /// Gets or sets the amount of bytes the full type record data requires. + /// + public uint TypeRecordsByteCount + { + get; + set; + } + + /// + /// Gets or sets the MSF stream index of the hash table for every record type in the stream (if available). + /// + /// + /// When this value is set to -1 (0xFFFF), then there is no hash stream stored in the PDB file. + /// + public ushort HashStreamIndex + { + get; + set; + } + + /// + /// Gets or sets the MSF stream index of the auxiliary hash table for every record type in the stream. + /// + /// + /// When this value is set to -1 (0xFFFF), then there is no hash stream stored in the PDB file. + /// The exact purpose of this stream is unknown, and usually this stream is not present. + /// + public ushort HashAuxStreamIndex + { + get; + set; + } + + /// + /// Gets or sets the number of bytes that a single hash value in the type hash stream consists of. + /// + public uint HashKeySize + { + get; + set; + } + + /// + /// Gets or sets the number of buckets used in the type record hash table. + /// + public uint HashBucketCount + { + get; + set; + } + + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of hash values. + /// + public uint HashValueBufferOffset + { + get; + set; + } + + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of hash values consists of. + /// + public uint HashValueBufferLength + { + get; + set; + } + + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of type record indices. + /// + public uint IndexOffsetBufferOffset + { + get; + set; + } + + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of type record indices consists of. + /// + public uint IndexOffsetBufferLength + { + get; + set; + } + + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of hash-index pairs. + /// + public uint HashAdjBufferOffset + { + get; + set; + } + + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of hash-index pairs consists of. + /// + public uint HashAdjBufferLength + { + get; + set; + } + + /// + /// Reads a TPI stream from the provided input stream. + /// + /// The input stream. + /// The TPI stream. + public static TpiStream FromReader(BinaryStreamReader reader) => new SerializedTpiStream(reader); + + /// + /// Attempts to get a reader object that starts at the beginning of a leaf record for the provided type index. + /// + /// The type index to get the reader for. + /// The obtained reader object. + /// + /// true if the provided type index was valid and a reader object was constructed successfully, + /// false otherwise. + /// + public abstract bool TryGetLeafRecordReader(uint typeIndex, out BinaryStreamReader reader); + + /// + /// Gets a reader object that starts at the beginning of a leaf record for the provided type index. + /// + /// The type index to get the reader for. + /// The obtained reader object. + /// Occurs when the provided type index was invalid. + public BinaryStreamReader GetLeafRecordReader(uint typeIndex) + { + if (!TryGetLeafRecordReader(typeIndex, out var reader)) + throw new ArgumentException("Invalid type index."); + + return reader; + } + + /// + public override uint GetPhysicalSize() => TpiStreamHeaderSize + TypeRecordsByteCount; + + /// + public override void Write(IBinaryStreamWriter writer) + { + WriteHeader(writer); + WriteTypeRecords(writer); + } + + private void WriteHeader(IBinaryStreamWriter writer) + { + writer.WriteUInt32((uint) Version); + writer.WriteUInt32(TpiStreamHeaderSize); + writer.WriteUInt32(TypeIndexBegin); + writer.WriteUInt32(TypeIndexEnd); + writer.WriteUInt32(TypeRecordsByteCount); + writer.WriteUInt16(HashStreamIndex); + writer.WriteUInt16(HashAuxStreamIndex); + writer.WriteUInt32(HashKeySize); + writer.WriteUInt32(HashBucketCount); + writer.WriteUInt32(HashValueBufferOffset); + writer.WriteUInt32(HashValueBufferLength); + writer.WriteUInt32(IndexOffsetBufferOffset); + writer.WriteUInt32(IndexOffsetBufferLength); + writer.WriteUInt32(HashAdjBufferOffset); + writer.WriteUInt32(HashAdjBufferLength); + } + + /// + /// Writes all type records stored in the TPI stream to the provided output stream. + /// + /// The output stream. + protected abstract void WriteTypeRecords(IBinaryStreamWriter writer); +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs new file mode 100644 index 000000000..bf65e07ec --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs @@ -0,0 +1,15 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +/// +/// Provides members defining all known file formats for the TPI stream. +/// +public enum TpiStreamVersion : uint +{ +#pragma warning disable CS1591 + V40 = 19950410, + V41 = 19951122, + V50 = 19961031, + V70 = 19990903, + V80 = 20040203, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs new file mode 100644 index 000000000..0abd232a9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Msf; +using AsmResolver.Symbols.Pdb.Records; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Represents a single Program Debug Database (PDB) image. +/// +public class PdbImage +{ + private IList? _symbols; + private ConcurrentDictionary _simpleTypes = new(); + + /// + /// Gets a collection of all symbols stored in the PDB image. + /// + public IList Symbols + { + get + { + if (_symbols is null) + Interlocked.CompareExchange(ref _symbols, GetSymbols(), null); + return _symbols; + } + } + + /// + /// Reads a PDB image from the provided input file. + /// + /// The path to the PDB file. + /// The read PDB image. + public static PdbImage FromFile(string path) => FromFile(MsfFile.FromFile(path)); + + /// + /// Reads a PDB image from the provided input file. + /// + /// The input file. + /// The read PDB image. + public static PdbImage FromFile(IInputFile file) => FromFile(MsfFile.FromFile(file)); + + /// + /// Interprets a byte array as a PDB image. + /// + /// The data to interpret. + /// The read PDB image. + public static PdbImage FromBytes(byte[] data) => FromFile(MsfFile.FromBytes(data)); + + /// + /// Reads an PDB image from the provided input stream reader. + /// + /// The reader. + /// The read PDB image. + public static PdbImage FromReader(BinaryStreamReader reader) => FromFile(MsfFile.FromReader(reader)); + + /// + /// Loads a PDB image from the provided MSF file. + /// + /// The MSF file. + /// The read PDB image. + public static PdbImage FromFile(MsfFile file) + { + return FromFile(file, new PdbReaderParameters(ThrowErrorListener.Instance)); + } + + /// + /// Loads a PDB image from the provided MSF file. + /// + /// The MSF file. + /// The parameters to use while reading the PDB image. + /// The read PDB image. + public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParameters) + { + return new SerializedPdbImage(file, readerParameters); + } + + /// + /// Attempts to obtain a type record from the TPI or IPI stream based on its type index. + /// + /// The type index. + /// The resolved type. + /// true if the type was found, false otherwise. + public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf) + { + typeIndex &= 0x7fffffff; + if (typeIndex is > 0 and < 0x1000) + { + leaf = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleTypeRecord(i)); + return true; + } + + leaf = null; + return false; + } + + /// + /// Obtains a type record from the TPI or IPI stream based on its type index. + /// + /// The type index. + /// The resolved type. + /// Occurs when the type index is invalid. + public CodeViewLeaf GetLeafRecord(uint typeIndex) + { + if (!TryGetLeafRecord(typeIndex, out var type)) + throw new ArgumentException("Invalid type index."); + return type; + } + + /// + /// Obtains a collection of symbols stored in the symbol record stream of the PDB image. + /// + /// The symbols. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSymbols() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs b/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs new file mode 100644 index 000000000..9e2939315 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs @@ -0,0 +1,34 @@ +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides a context in which a PDB image reader exists in. This includes the PDB image as well as reader parameters. +/// +public class PdbReaderContext +{ + /// + /// Creates a new PDB reader context. + /// + /// The image for which the data is to be read. + /// The parameters used while reading the PDB image. + public PdbReaderContext(SerializedPdbImage parentImage, PdbReaderParameters parameters) + { + ParentImage = parentImage; + Parameters = parameters; + } + + /// + /// Gets the image for which the data is read. + /// + public SerializedPdbImage ParentImage + { + get; + } + + /// + /// Gets the parameters used for reading the data. + /// + public PdbReaderParameters Parameters + { + get; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs b/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs new file mode 100644 index 000000000..7368424b0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs @@ -0,0 +1,33 @@ +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides parameters for configuring the reading process of a PDB image. +/// +public class PdbReaderParameters +{ + /// + /// Creates new PDB reader parameters. + /// + public PdbReaderParameters() + : this(ThrowErrorListener.Instance) + { + } + + /// + /// Creates new PDB reader parameters with the provided error listener object. + /// + /// The object used for receiving parser errors. + public PdbReaderParameters(IErrorListener errorListener) + { + ErrorListener = errorListener; + } + + /// + /// Gets or sets the object responsible for receiving and processing parser errors. + /// + public IErrorListener ErrorListener + { + get; + set; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs new file mode 100644 index 000000000..5869c3acd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs @@ -0,0 +1,40 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Records.Serialized; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a single symbol record within the symbol record stream of a PDB file. +/// +public abstract class CodeViewSymbol +{ + /// + /// Gets the type of symbol this record encodes. + /// + public abstract CodeViewSymbolType CodeViewSymbolType + { + get; + } + + /// + /// Reads a single symbol record from the input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream. + /// The read symbol. + public static CodeViewSymbol FromReader(PdbReaderContext context, ref BinaryStreamReader reader) + { + ushort length = reader.ReadUInt16(); + var type = (CodeViewSymbolType) reader.ReadUInt16(); + var dataReader = reader.ForkRelative(reader.RelativeOffset, (uint) (length - 2)); + reader.Offset += (ulong) (length - 2); + + return type switch + { + CodeViewSymbolType.Pub32 => new SerializedPublicSymbol(dataReader), + CodeViewSymbolType.Udt => new SerializedUserDefinedTypeSymbol(context, dataReader), + CodeViewSymbolType.Constant => new SerializedConstantSymbol(context, dataReader), + _ => new UnknownSymbol(type, dataReader.ReadToEnd()) + }; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs new file mode 100644 index 000000000..11bb6e3ad --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs @@ -0,0 +1,865 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +// Reference: microsoft-pdb/include/cvinfo.h + +/// +/// Provides members defining all symbol record types that can be stored in a PDB symbol stream. +/// +public enum CodeViewSymbolType : ushort +{ +#pragma warning disable CS1591 + /// + /// Indicates the symbol is a Compile flags symbol + /// + Compile = 0x0001, + + /// + /// Indicates the symbol is a Register variable + /// + Register16T = 0x0002, + + /// + /// Indicates the symbol is a constant symbol + /// + Constant16T = 0x0003, + + /// + /// Indicates the symbol is a User defined type + /// + Udt16T = 0x0004, + + /// + /// Indicates the symbol is a Start Search + /// + SSearch = 0x0005, + + /// + /// Indicates the symbol is a Block, procedure, "with" or thunk end + /// + End = 0x0006, + + /// + /// Indicates the symbol is a Reserve symbol space in $$Symbols table + /// + Skip = 0x0007, + + /// + /// Indicates the symbol is a Reserved symbol for CV internal use + /// + CVReserve = 0x0008, + + /// + /// Indicates the symbol is a path to object file name + /// + ObjnameSt = 0x0009, + + /// + /// Indicates the symbol is a end of argument/return list + /// + EndArg = 0x000a, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdt16T = 0x000b, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg16T = 0x000c, + + /// + /// Indicates the symbol is a return description symbol + /// + Return = 0x000d, + + /// + /// Indicates the symbol is a description of this pointer on entry + /// + EntryThis = 0x000e, + + /// + /// Indicates the symbol is a BP-relative + /// + BPRel16 = 0x0100, + + /// + /// Indicates the symbol is a Module-local symbol + /// + Ldata16 = 0x0101, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData16 = 0x0102, + + /// + /// Indicates the symbol is a a public symbol + /// + Pub16 = 0x0103, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc16 = 0x0104, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc16 = 0x0105, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk16 = 0x0106, + + /// + /// Indicates the symbol is a block start + /// + Block16 = 0x0107, + + /// + /// Indicates the symbol is a with start + /// + With16 = 0x0108, + + /// + /// Indicates the symbol is a code label + /// + Label16 = 0x0109, + + /// + /// Indicates the symbol is a change execution model + /// + CexModel16 = 0x010a, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable16 = 0x010b, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel16 = 0x010c, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel3216T = 0x0200, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData3216T = 0x0201, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData3216T = 0x0202, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub3216T = 0x0203, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc3216T = 0x0204, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc3216T = 0x0205, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk32St = 0x0206, + + /// + /// Indicates the symbol is a block start + /// + Block32St = 0x0207, + + /// + /// Indicates the symbol is a with start + /// + With32St = 0x0208, + + /// + /// Indicates the symbol is a code label + /// + Label32St = 0x0209, + + /// + /// Indicates the symbol is a change execution model + /// + CexModel32 = 0x020a, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable3216T = 0x020b, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel3216T = 0x020c, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread3216T = 0x020d, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread3216T = 0x020e, + + /// + /// Indicates the symbol is a static link for MIPS EH implementation + /// + SLink32 = 0x020f, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMip16T = 0x0300, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMip16T = 0x0301, + + // if these ref symbols have names following then the names are in ST format + /// + /// Indicates the symbol is a Reference to a procedure + /// + ProcRefSt = 0x0400, + + /// + /// Indicates the symbol is a Reference to data + /// + DataRefSt = 0x0401, + + /// + /// Indicates the symbol is a Used for page alignment of symbols + /// + Align = 0x0402, + + /// + /// Indicates the symbol is a Local Reference to a procedure + /// + LProcRefSt = 0x0403, + + /// + /// Indicates the symbol is a OEM defined symbol + /// + Oem = 0x0404, + + // sym records with 32-bit types embedded instead of 16-bit + // all have 0x1000 bit set for easy identification + // only do the 32-bit target versions since we don't really + // care about 16-bit ones anymore. + // TI16_MAX = 0x1000, + + /// + /// Indicates the symbol is a Register variable + /// + RegisterSt = 0x1001, + + /// + /// Indicates the symbol is a constant symbol + /// + ConstantSt = 0x1002, + + /// + /// Indicates the symbol is a User defined type + /// + UdtSt = 0x1003, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdtSt = 0x1004, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyRegSt = 0x1005, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel32St = 0x1006, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData32St = 0x1007, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData32St = 0x1008, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub32St = 0x1009, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc32St = 0x100a, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc32St = 0x100b, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable32 = 0x100c, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel32St = 0x100d, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread32St = 0x100e, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread32St = 0x100f, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMipSt = 0x1010, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMipSt = 0x1011, + + /// + /// Indicates the symbol is a extra frame and proc information + /// + FrameProc = 0x1012, + + /// + /// Indicates the symbol is a extended compile flags and info + /// + Compile2St = 0x1013, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg2St = 0x1014, + + /// + /// Indicates the symbol is a Local procedure start (IA64) + /// + LProcIa64St = 0x1015, + + /// + /// Indicates the symbol is a Global procedure start (IA64) + /// + GProcIa64St = 0x1016, + + /// + /// Indicates the symbol is a local IL sym with field for local slot index + /// + LocalSlotSt = 0x1017, + + /// + /// Indicates the symbol is a local IL sym with field for parameter slot index + /// + ParamSlotSt = 0x1018, + + /// + /// Indicates the symbol is a Annotation string literals + /// + Annotation = 0x1019, + + /// + /// Indicates the symbol is a Global proc + /// + GManProcSt = 0x101a, + + /// + /// Indicates the symbol is a Local proc + /// + LManProcSt = 0x101b, + + /// + /// Reserved + /// + Reserved1 = 0x101c, + + /// + /// Reserved + /// + Reserved2 = 0x101d, + + /// + /// Reserved + /// + Reserved3 = 0x101e, + + /// + /// Reserved + /// + RESERVED4 = 0x101f, + + LManDataSt = 0x1020, + + GManDataSt = 0x1021, + + ManFrameRelSt = 0x1022, + + ManRegisterSt = 0x1023, + + ManSlotSt = 0x1024, + + ManManyRegSt = 0x1025, + + ManRegRelSt = 0x1026, + + ManManyReg2St = 0x1027, + + /// + /// Indicates the symbol is a Index for type referenced by name from metadata + /// + ManTypRef = 0x1028, + + /// + /// Indicates the symbol is a Using namespace + /// + UNamespaceSt = 0x1029, + + // Symbols w/ SZ name fields. All name fields contain utf8 encoded strings. + /// + /// Indicates the symbol is a starting point for SZ name symbols + /// + StMax = 0x1100, + + /// + /// Indicates the symbol is a path to object file name + /// + ObjName = 0x1101, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk32 = 0x1102, + + /// + /// Indicates the symbol is a block start + /// + Block32 = 0x1103, + + /// + /// Indicates the symbol is a with start + /// + With32 = 0x1104, + + /// + /// Indicates the symbol is a code label + /// + Label32 = 0x1105, + + /// + /// Indicates the symbol is a Register variable + /// + Register = 0x1106, + + /// + /// Indicates the symbol is a constant symbol + /// + Constant = 0x1107, + + /// + /// Indicates the symbol is a User defined type + /// + Udt = 0x1108, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdt = 0x1109, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg = 0x110a, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel32 = 0x110b, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData32 = 0x110c, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData32 = 0x110d, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub32 = 0x110e, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc32 = 0x110f, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc32 = 0x1110, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel32 = 0x1111, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread32 = 0x1112, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread32 = 0x1113, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMips = 0x1114, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMips = 0x1115, + + /// + /// Indicates the symbol is a extended compile flags and info + /// + Compile2 = 0x1116, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg2 = 0x1117, + + /// + /// Indicates the symbol is a Local procedure start (IA64) + /// + LprocIa64 = 0x1118, + + /// + /// Indicates the symbol is a Global procedure start (IA64) + /// + GProcIa64 = 0x1119, + + /// + /// Indicates the symbol is a local IL sym with field for local slot index + /// + LocalSlot = 0x111a, + + /// + /// Indicates the symbol is a alias for LOCALSLOT + /// + Slot = LocalSlot, + + /// + /// Indicates the symbol is a local IL sym with field for parameter slot index + /// + ParamSlot = 0x111b, + + // symbols to support managed code debugging + LManData = 0x111c, + GManData = 0x111d, + ManFrameRel = 0x111e, + ManRegister = 0x111f, + ManSlot = 0x1120, + ManManyReg = 0x1121, + ManRegRel = 0x1122, + ManManyReg2 = 0x1123, + + /// + /// Indicates the symbol is a Using namespace + /// + UNamespace = 0x1124, + + // ref symbols with name fields + /// + /// Indicates the symbol is a Reference to a procedure + /// + ProcRef = 0x1125, + + /// + /// Indicates the symbol is a Reference to data + /// + DataRef = 0x1126, + + /// + /// Indicates the symbol is a Local Reference to a procedure + /// + LProcRef = 0x1127, + + /// + /// Indicates the symbol is a Reference to an ANNOTATION symbol + /// + AnnotationRef = 0x1128, + + /// + /// Indicates the symbol is a Reference to one of the many MANPROCSYM's + /// + TokenRef = 0x1129, + + // continuation of managed symbols + /// + /// Indicates the symbol is a Global proc + /// + GManProc = 0x112a, + + /// + /// Indicates the symbol is a Local proc + /// + LManProc = 0x112b, + + // short, light-weight thunks + /// + /// Indicates the symbol is a trampoline thunks + /// + Trampoline = 0x112c, + + /// + /// Indicates the symbol is a constants with metadata type info + /// + ManConstant = 0x112d, + + // native attributed local/parms + /// + /// Indicates the symbol is a relative to virtual frame ptr + /// + AttrFrameRel = 0x112e, + + /// + /// Indicates the symbol is a stored in a register + /// + AttrRegister = 0x112f, + + /// + /// Indicates the symbol is a relative to register (alternate frame ptr) + /// + AttrRegRel = 0x1130, + + /// + /// Indicates the symbol is a stored in >1 register + /// + AttrManyReg = 0x1131, + + // Separated code (from the compiler) support + SepCode = 0x1132, + + /// + /// Indicates the symbol is a defines a local symbol in optimized code + /// + Local2005 = 0x1133, + + /// + /// Indicates the symbol is a defines a single range of addresses in which symbol can be evaluated + /// + DefRange2005 = 0x1134, + + /// + /// Indicates the symbol is a defines ranges of addresses in which symbol can be evaluated + /// + DefRange22005 = 0x1135, + + /// + /// Indicates the symbol is a A COFF section in a PE executable + /// + Section = 0x1136, + + /// + /// Indicates the symbol is a A COFF group + /// + CoffGroup = 0x1137, + + /// + /// Indicates the symbol is a A export + /// + Export = 0x1138, + + /// + /// Indicates the symbol is a Indirect call site information + /// + CallSiteInfo = 0x1139, + + /// + /// Indicates the symbol is a Security cookie information + /// + FrameCookie = 0x113a, + + /// + /// Indicates the symbol is a Discarded by LINK /OPT:REF (experimental, see richards) + /// + Discarded = 0x113b, + + /// + /// Indicates the symbol is a Replacement for COMPILE2 + /// + Compile3 = 0x113c, + + /// + /// Indicates the symbol is a Environment block split off from COMPILE2 + /// + EnvBlock = 0x113d, + + /// + /// Indicates the symbol is a defines a local symbol in optimized code + /// + Local = 0x113e, + + /// + /// Indicates the symbol is a defines a single range of addresses in which symbol can be evaluated + /// + DefRange = 0x113f, + + /// + /// Indicates the symbol is a ranges for a subfield + /// + DefRangeSubField = 0x1140, + + /// + /// Indicates the symbol is a ranges for en-registered symbol + /// + DefRangeRegister = 0x1141, + + /// + /// Indicates the symbol is a range for stack symbol. + /// + DefRangeFramePointerRel = 0x1142, + + /// + /// Indicates the symbol is a ranges for en-registered field of symbol + /// + DefRangeSubFieldRegister = 0x1143, + + /// + /// Indicates the symbol is a range for stack symbol span valid full scope of function body, gap might apply. + /// + DefRangeFramePointerRelFullScope = 0x1144, + + /// + /// Indicates the symbol is a range for symbol address as register + offset. + /// + DefRangeRegisterRel = 0x1145, + + // PROC symbols that reference ID instead of type + LProc32Id = 0x1146, + GProc32Id = 0x1147, + LProcMipId = 0x1148, + GProcMipId = 0x1149, + LProcIa64Id = 0x114a, + GProcIa64Id = 0x114b, + + /// + /// Indicates the symbol is a build information. + /// + BuildInfo = 0x114c, + + /// + /// Indicates the symbol is a inlined function callsite. + /// + InlineSite = 0x114d, + InlineSiteEnd = 0x114e, + ProcIdEnd = 0x114f, + + DefRangeHlsl = 0x1150, + GDataHlsl = 0x1151, + LDataHlsl = 0x1152, + + FileStatic = 0x1153, + + /// + /// Indicates the symbol is a DPC groupshared variable + /// + LocalDpcGroupShared = 0x1154, + + /// + /// Indicates the symbol is a DPC local procedure start + /// + LProc32Dpc = 0x1155, + LProc32DpcId = 0x1156, + + /// + /// Indicates the symbol is a DPC pointer tag definition range + /// + DefRangeDpcPtrTag = 0x1157, + + /// + /// Indicates the symbol is a DPC pointer tag value to symbol record map + /// + DpcSymTagMap = 0x1158, + + ArmSwitchTable = 0x1159, + Callees = 0x115a, + Callers = 0x115b, + PogoData = 0x115c, + + /// + /// Indicates the symbol is a extended inline site information + /// + InlineSite2 = 0x115d, + + /// + /// Indicates the symbol is a heap allocation site + /// + HeapAllocSite = 0x115e, + + /// + /// Indicates the symbol is a only generated at link time + /// + ModTypeRef = 0x115f, + + /// + /// Indicates the symbol is a only generated at link time for mini PDB + /// + RefMiniPdb = 0x1160, + + /// + /// Indicates the symbol is a only generated at link time for mini PDB + /// + PdbMap = 0x1161, + + GDataHlsl32 = 0x1162, + LDataHlsl32 = 0x1163, + + GDataHlsl32Ex = 0x1164, + LDataHlsl32Ex = 0x1165, + + RecTypeMax, // one greater than last + RecTypeLast = RecTypeMax - 1, + RecTypePad = RecTypeMax + 0x100 // Used *only* to verify symbol record types so that current PDB code can potentially read + // future PDBs (assuming no format change, etc). +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs new file mode 100644 index 000000000..5bdc45c4c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs @@ -0,0 +1,85 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a single constant symbol. +/// +public class ConstantSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + private readonly LazyVariable _type; + + /// + /// Initializes a named constant + /// + protected ConstantSymbol() + { + _name = new LazyVariable(GetName); + _type = new LazyVariable(GetConstantType); + } + + /// + /// Defines a new named constant. + /// + /// The name of the type. + /// The type. + /// The value to assign to the constant. + public ConstantSymbol(Utf8String name, CodeViewTypeRecord type, ushort value) + { + _name = new LazyVariable(name); + _type = new LazyVariable(type); + Value = value; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Constant; + + /// + /// Gets or sets the value type of the constant. + /// + public CodeViewTypeRecord Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the numerical value assigned to the constant. + /// + public ushort Value + { + get; + set; + } + + /// + /// Gets or sets the name of the constant. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the constant. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the value type of the constant. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetConstantType() => null; + + /// + public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name} = {Value}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs new file mode 100644 index 000000000..08515a42b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs @@ -0,0 +1,123 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a public symbol stored in a PDB symbol stream. +/// +public class PublicSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + + /// + /// Initializes a new empty public symbol. + /// + protected PublicSymbol() + { + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new public symbol. + /// + /// The segment index. + /// The offset within the segment the symbol starts at. + /// The name of the symbol. + /// The attributes associated to the symbol. + public PublicSymbol(ushort segment, uint offset, Utf8String name, PublicSymbolAttributes attributes) + { + Segment = segment; + Offset = offset; + _name = new LazyVariable(name); + Attributes = attributes; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Pub32; + + /// + /// Gets or sets the file segment index this symbol is located in. + /// + public ushort Segment + { + get; + set; + } + + /// + /// Gets or sets the offset within the file that this symbol is defined at. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets attributes associated to the public symbol. + /// + public PublicSymbolAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether the symbol is a code symbol. + /// + public bool IsCode + { + get => (Attributes & PublicSymbolAttributes.Code) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Code) + | (value ? PublicSymbolAttributes.Code : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol is a function symbol. + /// + public bool IsFunction + { + get => (Attributes & PublicSymbolAttributes.Function) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Function) + | (value ? PublicSymbolAttributes.Function : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol involves managed code. + /// + public bool IsManaged + { + get => (Attributes & PublicSymbolAttributes.Managed) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Managed) + | (value ? PublicSymbolAttributes.Managed : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol involves MSIL code. + /// + public bool IsMsil + { + get => (Attributes & PublicSymbolAttributes.Msil) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Msil) + | (value ? PublicSymbolAttributes.Msil : 0); + } + + /// + /// Gets or sets the name of the symbol. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the public symbol. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => $"{CodeViewSymbolType}: [{Segment:X4}:{Offset:X8}] {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs new file mode 100644 index 000000000..a5e5824bd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs @@ -0,0 +1,35 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members defining all flags that can be associated to a public symbol. +/// +[Flags] +public enum PublicSymbolAttributes : uint +{ + /// + /// Indicates no flags are assigned to the symbol. + /// + None = 0, + + /// + /// Indicates the symbol is a code symbol. + /// + Code = 0x00000001, + + /// + /// Indicates the symbol is a function. + /// + Function = 0x00000002, + + /// + /// Indicates the symbol involves managed code. + /// + Managed = 0x00000004, + + /// + /// Indicates the symbol involves MSIL code. + /// + Msil = 0x00000008, +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs new file mode 100644 index 000000000..3c3c31c53 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs @@ -0,0 +1,39 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedConstantSymbol : ConstantSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a constant symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedConstantSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _typeIndex = reader.ReadUInt32(); + Value = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetConstantType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Constant contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs new file mode 100644 index 000000000..f1a815036 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs @@ -0,0 +1,26 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedPublicSymbol : PublicSymbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a public symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedPublicSymbol(BinaryStreamReader reader) + { + Attributes = (PublicSymbolAttributes) reader.ReadUInt32(); + Offset = reader.ReadUInt32(); + Segment = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs new file mode 100644 index 000000000..fdcce9a7f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs @@ -0,0 +1,38 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedUserDefinedTypeSymbol : UserDefinedTypeSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a user-defined type symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _typeIndex = reader.ReadUInt32(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetSymbolType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"User-defined type contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs new file mode 100644 index 000000000..983173876 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs @@ -0,0 +1,35 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol record for which the format is unknown or unsupported. +/// +public class UnknownSymbol : CodeViewSymbol +{ + /// + /// Creates a new unknown symbol record. + /// + /// The type of symbol. + /// The raw data stored in the record. + public UnknownSymbol(CodeViewSymbolType codeViewSymbolType, byte[] data) + { + CodeViewSymbolType = codeViewSymbolType; + Data = data; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType + { + get; + } + + /// + /// Gets the raw data stored in the record. + /// + public byte[] Data + { + get; + } + + /// + public override string ToString() => $"{CodeViewSymbolType.ToString()} ({Data.Length.ToString()} bytes)"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs new file mode 100644 index 000000000..4e84a3aa3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -0,0 +1,74 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a user-defined type symbol in a PDB symbol stream, mapping a symbol to a type in the TPI stream. +/// +public class UserDefinedTypeSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + private readonly LazyVariable _type; + + /// + /// Initializes a new empty user-defined type symbol. + /// + protected UserDefinedTypeSymbol() + { + _name = new LazyVariable(GetName); + _type = new LazyVariable(GetSymbolType); + } + + /// + /// Defines a new user-defined type. + /// + /// The name of the type. + /// The type. + public UserDefinedTypeSymbol(Utf8String name, CodeViewTypeRecord type) + { + _name = new LazyVariable(name); + _type = new LazyVariable(type); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Udt; + + /// + /// Gets or sets the name of the type. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Gets or sets the index associated to the type. + /// + public CodeViewTypeRecord Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Obtains the new name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the type that is referenced by this symbol. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetSymbolType() => null; + + /// + public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs new file mode 100644 index 000000000..32bc7dcd6 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; +using AsmResolver.Symbols.Pdb.Metadata.Info; +using AsmResolver.Symbols.Pdb.Metadata.Tpi; +using AsmResolver.Symbols.Pdb.Msf; +using AsmResolver.Symbols.Pdb.Records; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides an implementation for a PDB image that is read from an input MSF file. +/// +public class SerializedPdbImage : PdbImage +{ + private const int MinimalRequiredStreamCount = 5; + private readonly MsfFile _file; + private CodeViewLeaf?[]? _leaves; + + /// + /// Interprets a PDB image from the provided MSF file. + /// + /// The MSF file to read from. + /// The parameters to use while reading the PDB image. + public SerializedPdbImage(MsfFile file, PdbReaderParameters readerParameters) + { + _file = file; + + if (file.Streams.Count < MinimalRequiredStreamCount) + throw new BadImageFormatException("MSF does not contain the minimal required amount of streams."); + + InfoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); + DbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + TpiStream = TpiStream.FromReader(file.Streams[TpiStream.StreamIndex].CreateReader()); + + ReaderContext = new PdbReaderContext(this, readerParameters); + } + + internal PdbReaderContext ReaderContext + { + get; + } + + internal InfoStream InfoStream + { + get; + } + + internal DbiStream DbiStream + { + get; + } + + internal TpiStream TpiStream + { + get; + } + + [MemberNotNull(nameof(_leaves))] + private void EnsureLeavesInitialized() + { + if (_leaves is null) + { + Interlocked.CompareExchange(ref _leaves, + new CodeViewLeaf?[TpiStream.TypeIndexEnd - TpiStream.TypeIndexBegin], null); + } + } + + /// + public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf) + { + if (typeIndex < TpiStream.TypeIndexBegin) + return base.TryGetLeafRecord(typeIndex, out leaf); + + EnsureLeavesInitialized(); + + if (typeIndex >= TpiStream.TypeIndexBegin && typeIndex < TpiStream.TypeIndexEnd) + { + leaf = _leaves[typeIndex - TpiStream.TypeIndexBegin]; + if (leaf is null && TpiStream.TryGetLeafRecordReader(typeIndex, out var reader)) + { + leaf = CodeViewLeaf.FromReader(ReaderContext, typeIndex, ref reader); + Interlocked.CompareExchange(ref _leaves[typeIndex - TpiStream.TypeIndexBegin], leaf, null); + } + + leaf = _leaves[typeIndex - TpiStream.TypeIndexBegin]; + return leaf is not null; + } + + leaf = null; + return false; + } + + /// + protected override IList GetSymbols() + { + var result = new List(); + + int index = DbiStream.SymbolRecordStreamIndex; + if (index >= _file.Streams.Count) + return result; + + var reader = _file.Streams[DbiStream.SymbolRecordStreamIndex].CreateReader(); + while (reader.CanRead(sizeof(ushort) * 2)) + result.Add(CodeViewSymbol.FromReader(ReaderContext, ref reader)); + + return result; + } +} diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index 07dbf2281..b7685bc8a 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver/Collections/BitList.cs b/src/AsmResolver/Collections/BitList.cs new file mode 100644 index 000000000..c4224cd04 --- /dev/null +++ b/src/AsmResolver/Collections/BitList.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace AsmResolver.Collections +{ + /// + /// Represents a bit vector that can be resized dynamically. + /// + public class BitList : IList + { + private const int WordSize = sizeof(int) * 8; + private uint[] _words; + private int _version; + + /// + /// Creates a new bit list. + /// + public BitList() + { + _words = new uint[1]; + } + + /// + /// Creates a new bit list. + /// + /// The initial number of bits that the buffer should at least be able to store. + public BitList(int capacity) + { + _words = new uint[((uint) capacity).Align(WordSize)]; + } + + /// + public int Count + { + get; + private set; + } + + /// + public bool IsReadOnly => false; + + /// + public bool this[int index] + { + get + { + if (index >= Count) + throw new IndexOutOfRangeException(); + + (int wordIndex, int bitIndex) = SplitWordBitIndex(index); + return (_words[wordIndex] >> bitIndex & 1) != 0; + } + set + { + if (index >= Count) + throw new IndexOutOfRangeException(); + + (int wordIndex, int bitIndex) = SplitWordBitIndex(index); + _words[wordIndex] = (_words[wordIndex] & ~(1u << bitIndex)) | (value ? 1u << bitIndex : 0u); + _version++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static (int wordIndex, int bitIndex) SplitWordBitIndex(int index) + { + int wordIndex = Math.DivRem(index, WordSize, out int offset); + return (wordIndex, offset); + } + + /// + public void Add(bool item) + { + EnsureCapacity(Count + 1); + Count++; + this[Count - 1] = item; + _version++; + } + + /// + public void Clear() => Count = 0; + + /// + public bool Contains(bool item) => IndexOf(item) != -1; + + /// + public void CopyTo(bool[] array, int arrayIndex) + { + for (int i = 0; i < Count; i++) + array[arrayIndex + i] = this[i]; + } + + /// + public bool Remove(bool item) + { + int index = IndexOf(item); + if (index == -1) + return false; + + RemoveAt(index); + return true; + } + + /// + public int IndexOf(bool item) + { + for (int i = 0; i < Count; i++) + { + (int wordIndex, int bitIndex) = SplitWordBitIndex(i); + if ((_words[wordIndex] >> bitIndex & 1) != 0 == item) + return i; + } + + return -1; + } + + /// + public void Insert(int index, bool item) + { + if (index > Count) + throw new IndexOutOfRangeException(); + + EnsureCapacity(Count++); + (int wordIndex, int bitIndex) = SplitWordBitIndex(index); + + uint carry = _words[wordIndex] & (1u << (WordSize - 1)); + + // Insert bit into current word. + uint lowerMask = (1u << bitIndex) - 1; + uint upperMask = ~lowerMask; + _words[wordIndex] = (_words[wordIndex] & upperMask) << 1 // Shift left-side of the bit index by one + | (item ? 1u << bitIndex : 0u) // Insert bit. + | (_words[wordIndex] & lowerMask); // Keep right-side of the bit. + + for (int i = wordIndex + 1; i < _words.Length; i++) + { + uint nextCarry = _words[i] & (1u << (WordSize - 1)); + _words[i] = (_words[i] << 1) | (carry >> (WordSize - 1)); + carry = nextCarry; + } + + _version++; + } + + /// + public void RemoveAt(int index) + { + Count--; + (int wordIndex, int bitIndex) = SplitWordBitIndex(index); + + // Note we check both word count and actual bit count. Words in the buffer might contain garbage data for + // every bit index i >= Count. Also, there might be exactly enough words allocated for Count bits, i.e. + // there might not be a "next" word. + uint borrow = wordIndex + 1 < _words.Length && ((uint) index).Align(WordSize) < Count + ? _words[wordIndex + 1] & 1 + : 0; + + uint lowerMask = (1u << bitIndex) - 1; + uint upperMask = ~((1u << (bitIndex + 1)) - 1); + _words[wordIndex] = (_words[wordIndex] & upperMask) >> 1 // Shift left-side of the bit index by one + | (_words[wordIndex] & lowerMask) // Keep right-side of the bit. + | borrow << (WordSize - 1); // Copy first bit of next word into last bit of current. + + for (int i = wordIndex + 1; i < _words.Length; i++) + { + uint nextBorrow = i + 1 < _words.Length && ((uint) index).Align(WordSize) < Count + ? _words[i + 1] & 1 + : 0; + + _words[i] = (_words[i] >> 1) | (borrow << (WordSize - 1)); + borrow = nextBorrow; + } + + _version++; + } + + /// + /// Ensures the provided number of bits can be stored in the bit list. + /// + /// The number of bits to store in the list. + public void EnsureCapacity(int capacity) + { + if (capacity < WordSize * _words.Length) + return; + + int newWordCount = (int) (((uint) capacity).Align(WordSize) / 8); + Array.Resize(ref _words, newWordCount); + } + + /// + /// Returns an enumerator for all bits in the bit vector. + /// + /// The enumerator. + public Enumerator GetEnumerator() => new(this); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Represents an enumerator that iterates over all bits in a bit list. + /// + public struct Enumerator : IEnumerator + { + private readonly BitList _list; + private readonly int _version; + private int _index = -1; + + /// + /// Creates a new bit enumerator. + /// + /// The list to enumerate. + public Enumerator(BitList list) + { + _version = list._version; + _list = list; + } + + /// + public bool MoveNext() + { + if (_version != _list._version) + throw new InvalidOperationException("Collection was modified."); + + if (_index >= _list.Count) + return false; + + _index++; + return true; + } + + /// + public void Reset() => _index = -1; + + /// + public bool Current => _list[_index]; + + /// + object IEnumerator.Current => Current; + + /// + public void Dispose() + { + } + } + } +} diff --git a/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/src/AsmResolver/DataSourceSegment.cs b/src/AsmResolver/DataSourceSegment.cs index a8df578f7..8b237906e 100644 --- a/src/AsmResolver/DataSourceSegment.cs +++ b/src/AsmResolver/DataSourceSegment.cs @@ -25,11 +25,11 @@ public DataSourceSegment(IDataSource dataSource, ulong offset, uint rva, uint si } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - long displacement = (long) newOffset - (long) _originalOffset; + long displacement = (long) parameters.Offset - (long) _originalOffset; _displacedDataSource = displacement != 0 ? new DisplacedDataSource(_dataSource, displacement) : null; diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index 74088ce4b..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. /// @@ -328,37 +332,68 @@ public byte[] ReadToEnd() /// /// The delimeter byte to stop at. /// The read bytes, including the delimeter if it was found. - public byte[] ReadBytesUntil(byte delimeter) + public byte[] ReadBytesUntil(byte delimeter) => ReadBytesUntil(delimeter, true); + + /// + /// Reads bytes from the input stream until the provided delimeter byte is reached. + /// + /// The delimeter byte to stop at. + /// + /// true if the final delimeter should be included in the return value, false otherwise. + /// + /// The read bytes. + /// + /// This function always consumes the delimeter from the input stream if it is present, regardless of the value + /// of . + /// + public byte[] ReadBytesUntil(byte delimeter, bool includeDelimeterInReturn) { var lookahead = Fork(); - while (lookahead.RelativeOffset < lookahead.Length) - { - byte b = lookahead.ReadByte(); - if (b == delimeter) - break; - } + bool hasConsumedDelimeter = lookahead.AdvanceUntil(delimeter, includeDelimeterInReturn); byte[] buffer = new byte[lookahead.RelativeOffset - RelativeOffset]; ReadBytes(buffer, 0, buffer.Length); + + if (hasConsumedDelimeter) + ReadByte(); + return buffer; } /// - /// Reads a null-terminated ASCII string from the input stream. + /// Advances the reader until the provided delimeter byte is reached. /// - /// The read ASCII string, excluding the null terminator. - public string ReadAsciiString() + /// The delimeter byte to stop at. + /// + /// true if the final delimeter should be consumed if available, false otherwise. + /// + /// true if the delimeter byte was found and consumed, false otherwise. + public bool AdvanceUntil(byte delimeter, bool consumeDelimeter) { - byte[] data = ReadBytesUntil(0); - int length = data.Length; + while (RelativeOffset < Length) + { + byte b = ReadByte(); + if (b == delimeter) + { + if (!consumeDelimeter) + { + RelativeOffset--; + return true; + } - // Exclude trailing 0 byte. - if (data[data.Length - 1] == 0) - length--; + return false; + } + } - return Encoding.ASCII.GetString(data, 0, length); + return false; } + /// + /// Reads a null-terminated ASCII string from the input stream. + /// + /// The read ASCII string, excluding the null terminator. + public string ReadAsciiString() => Encoding.ASCII.GetString(ReadBytesUntil(0, false)); + /// /// Reads a zero-terminated Unicode string from the stream. /// @@ -378,6 +413,18 @@ public string ReadUnicodeString() return builder.ToString(); } + /// + /// Reads a null-terminated UTF-8 string from the input stream. + /// + /// The read UTF-8 string, excluding the null terminator. + public Utf8String ReadUtf8String() + { + byte[] data = ReadBytesUntil(0, false); + return data.Length != 0 + ? new Utf8String(data) + : Utf8String.Empty; + } + /// /// Reads either a 32-bit or a 64-bit number from the input stream. /// diff --git a/src/AsmResolver/IOffsetProvider.cs b/src/AsmResolver/IOffsetProvider.cs index 185b9c85e..5f83e16e9 100644 --- a/src/AsmResolver/IOffsetProvider.cs +++ b/src/AsmResolver/IOffsetProvider.cs @@ -20,20 +20,6 @@ uint Rva { get; } - - /// - /// Determines whether this structure can be relocated to another offset or virtual address. - /// - bool CanUpdateOffsets - { - get; - } - - /// - /// Assigns a new file and virtual offset to the segment and all its sub-components. - /// - /// The new file offset. - /// The new virtual offset. - void UpdateOffsets(ulong newOffset, uint newRva); } + } diff --git a/src/AsmResolver/ISegment.cs b/src/AsmResolver/ISegment.cs index f24ea98c1..dce4aec3c 100644 --- a/src/AsmResolver/ISegment.cs +++ b/src/AsmResolver/ISegment.cs @@ -9,12 +9,26 @@ namespace AsmResolver /// public interface ISegment : IOffsetProvider, IWritable { + /// + /// Determines whether this structure can be relocated to another offset or virtual address. + /// + bool CanUpdateOffsets + { + get; + } + /// /// Computes the number of bytes the segment will contain when it is mapped into memory. /// /// The number of bytes. uint GetVirtualSize(); + /// + /// Assigns a new file and virtual offset to the segment and all its sub-components. + /// + /// The parameters containing the new offset information for the segment. + void UpdateOffsets(in RelocationParameters parameters); + } public static partial class Extensions diff --git a/src/AsmResolver/ISegmentReferenceResolver.cs b/src/AsmResolver/ISegmentReferenceFactory.cs similarity index 86% rename from src/AsmResolver/ISegmentReferenceResolver.cs rename to src/AsmResolver/ISegmentReferenceFactory.cs index 7f9db42e9..7fff84851 100644 --- a/src/AsmResolver/ISegmentReferenceResolver.cs +++ b/src/AsmResolver/ISegmentReferenceFactory.cs @@ -1,15 +1,15 @@ -namespace AsmResolver -{ - /// - /// Provides members for resolving virtual addresses to a segment in a binary file. - /// - public interface ISegmentReferenceResolver - { - /// - /// Resolves the provided virtual address to a segment reference. - /// - /// The virtual address of the segment. - /// The reference to the segment. - ISegmentReference GetReferenceToRva(uint rva); - } -} \ No newline at end of file +namespace AsmResolver +{ + /// + /// Provides members for resolving virtual addresses to a segment in a binary file. + /// + public interface ISegmentReferenceFactory + { + /// + /// Resolves the provided virtual address to a segment reference. + /// + /// The virtual address of the segment. + /// The reference to the segment. + ISegmentReference GetReferenceToRva(uint rva); + } +} diff --git a/src/AsmResolver/RelativeReference.cs b/src/AsmResolver/RelativeReference.cs index cc31132fd..24fa44250 100644 --- a/src/AsmResolver/RelativeReference.cs +++ b/src/AsmResolver/RelativeReference.cs @@ -41,13 +41,6 @@ public int Additive /// public uint Rva => (uint) (Base.Rva + Additive); - /// - public bool CanUpdateOffsets => Base.CanUpdateOffsets; - - /// - public void UpdateOffsets(ulong newOffset, uint newRva) => - Base.UpdateOffsets( newOffset - (ulong) Additive, (uint) (newRva - Additive)); - /// public bool CanRead => Base is ISegmentReference reference && reference.CanRead; diff --git a/src/AsmResolver/RelocationParameters.cs b/src/AsmResolver/RelocationParameters.cs new file mode 100644 index 000000000..3ef4f5ad0 --- /dev/null +++ b/src/AsmResolver/RelocationParameters.cs @@ -0,0 +1,130 @@ +namespace AsmResolver +{ + /// + /// Provides parameters for relocating a segment to a new offset-rva pair. + /// + public struct RelocationParameters + { + /// + /// Creates new relocation parameters. + /// + /// The new offset of the segment. + /// The new virtual address of the segment, relative to the image base. + public RelocationParameters(ulong offset, uint rva) + : this(0, offset, rva, true) + { + } + + /// + /// Creates new relocation parameters. + /// + /// The base address of the image the segment is located in. + /// The new offset of the segment. + /// The new virtual address of the segment, relative to the image base. + /// true if the image is targeting 32-bit images, false for 64-bit images. + public RelocationParameters(ulong imageBase, ulong offset, uint rva, bool is32Bit) + { + ImageBase = imageBase; + Offset = offset; + Rva = rva; + Is32Bit = is32Bit; + } + + /// + /// Gets the image base that is assumed when relocating the segment. + /// + public ulong ImageBase + { + get; + } + + /// + /// Gets the new physical offset of the segment. + /// + public ulong Offset + { + get; + set; + } + + /// + /// Gets the new virtual address of the segment, relative to the image base. + /// + public uint Rva + { + get; + set; + } + + /// + /// Gets a value indicating whether the image is targeting 32-bit machines. + /// + public bool Is32Bit + { + get; + } + + /// + /// Gets a value indicating whether the image is targeting 64-bit machines. + /// + public bool Is64Bit => !Is32Bit; + + /// + /// Copies the current relocation parameters, and assigns a new offset and relative virtual address. + /// + /// The new offset. + /// The new relative virtual address. + public RelocationParameters WithOffsetRva(ulong offset, uint rva) + { + return new RelocationParameters(ImageBase, offset, rva, Is32Bit); + } + + /// + /// Aligns the current offset and virtual address to the nearest multiple of the provided alignment. + /// + /// The alignment. + public void Align(uint alignment) + { + Offset = Offset.Align(alignment); + Rva = Rva.Align(alignment); + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance with. + /// The new relocation parameters. + public readonly RelocationParameters WithAdvance(uint count) + { + return new RelocationParameters(ImageBase, Offset + count, Rva + count, Is32Bit); + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance with. + /// The new relocation parameters. + public void Advance(uint count) + { + Offset += count; + Rva += count; + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance the physical offset with. + /// The number of bytes to advance the virtual address with. + public void Advance(uint physicalCount, uint virtualCount) + { + Offset += physicalCount; + Rva += virtualCount; + } + + /// + public override string ToString() + { + return $"{nameof(ImageBase)}: {ImageBase:X8}, {nameof(Offset)}: {Offset:X8}, {nameof(Rva)}: {Rva:X8}"; + } + } +} diff --git a/src/AsmResolver/SegmentBase.cs b/src/AsmResolver/SegmentBase.cs index 6013990ef..264e1925a 100644 --- a/src/AsmResolver/SegmentBase.cs +++ b/src/AsmResolver/SegmentBase.cs @@ -25,10 +25,10 @@ public uint Rva public bool CanUpdateOffsets => true; /// - public virtual void UpdateOffsets(ulong newOffset, uint newRva) + public virtual void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; } /// diff --git a/src/AsmResolver/SegmentBuilder.cs b/src/AsmResolver/SegmentBuilder.cs index 734d917d5..1bfd17eb3 100644 --- a/src/AsmResolver/SegmentBuilder.cs +++ b/src/AsmResolver/SegmentBuilder.cs @@ -55,31 +55,25 @@ public void Add(ISegment segment, uint alignment) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; - _physicalSize = 0; - _virtualSize = 0; + Offset = parameters.Offset; + Rva = parameters.Rva; + var current = parameters; foreach (var item in _items) { - uint physicalPadding = (uint) (newOffset.Align(item.Alignment) - newOffset); - uint virtualPadding = newRva.Align(item.Alignment) - newRva; - - newOffset += physicalPadding; - newRva += virtualPadding; - - item.Segment.UpdateOffsets(newOffset, newRva); + current.Align(item.Alignment); + item.Segment.UpdateOffsets(current); uint physicalSize = item.Segment.GetPhysicalSize(); uint virtualSize = item.Segment.GetVirtualSize(); - newOffset += physicalSize; - newRva += virtualSize; - _physicalSize += physicalPadding + physicalSize; - _virtualSize += virtualPadding + virtualSize; + current.Advance(physicalSize, virtualSize); } + + _physicalSize = (uint) (current.Offset - parameters.Offset); + _virtualSize = current.Rva - parameters.Rva; } /// diff --git a/src/AsmResolver/SegmentReference.cs b/src/AsmResolver/SegmentReference.cs index cb9aa1e0d..ecda7b3e2 100644 --- a/src/AsmResolver/SegmentReference.cs +++ b/src/AsmResolver/SegmentReference.cs @@ -32,9 +32,6 @@ public SegmentReference(ISegment? segment) /// public uint Rva => Segment?.Rva ?? 0; - /// - public bool CanUpdateOffsets => Segment?.CanUpdateOffsets ?? false; - /// public bool IsBounded => true; @@ -50,9 +47,6 @@ public ISegment? Segment get; } - /// - public void UpdateOffsets(ulong newOffset, uint newRva) => Segment?.UpdateOffsets(newOffset, newRva); - /// public BinaryStreamReader CreateReader() { diff --git a/src/AsmResolver/VirtualAddress.cs b/src/AsmResolver/VirtualAddress.cs new file mode 100644 index 000000000..7975ba7a6 --- /dev/null +++ b/src/AsmResolver/VirtualAddress.cs @@ -0,0 +1,37 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver +{ + /// + /// Represents a (relative) virtual address in a file. + /// + public sealed class VirtualAddress : ISegmentReference + { + /// + /// Wraps a relative virtual address into a object. + /// + /// + public VirtualAddress(uint rva) + { + Rva = rva; + } + + ulong IOffsetProvider.Offset => Rva; + + /// + public uint Rva + { + get; + } + + /// + public bool CanRead => false; + + bool ISegmentReference.IsBounded => false; + + BinaryStreamReader ISegmentReference.CreateReader() => throw new InvalidOperationException(); + + ISegment? ISegmentReference.GetSegment() => throw new InvalidOperationException(); + } +} diff --git a/src/AsmResolver/VirtualAddressFactory.cs b/src/AsmResolver/VirtualAddressFactory.cs new file mode 100644 index 000000000..b5af70799 --- /dev/null +++ b/src/AsmResolver/VirtualAddressFactory.cs @@ -0,0 +1,19 @@ +namespace AsmResolver +{ + /// + /// Provides an implementation of a reference factory that constructs objects. + /// + public class VirtualAddressFactory : ISegmentReferenceFactory + { + /// + /// Gets the default instance of this factory. + /// + public static VirtualAddressFactory Instance + { + get; + } = new(); + + /// + public ISegmentReference GetReferenceToRva(uint rva) => new VirtualAddress(rva); + } +} diff --git a/src/AsmResolver/VirtualSegment.cs b/src/AsmResolver/VirtualSegment.cs index 5b31c2e39..dd2b08022 100644 --- a/src/AsmResolver/VirtualSegment.cs +++ b/src/AsmResolver/VirtualSegment.cs @@ -44,15 +44,7 @@ public uint VirtualSize public ulong Offset => PhysicalContents?.Offset ?? 0; /// - public uint Rva - { - get => _rva; - set - { - _rva = value; - PhysicalContents?.UpdateOffsets(Offset, value); - } - } + public uint Rva => _rva; /// public bool CanUpdateOffsets => PhysicalContents?.CanUpdateOffsets ?? false; @@ -64,10 +56,10 @@ public uint Rva public bool IsReadable => PhysicalContents is IReadableSegment; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - _rva = newRva; - PhysicalContents?.UpdateOffsets(newOffset, newRva); + _rva = parameters.Rva; + PhysicalContents?.UpdateOffsets(parameters); } /// diff --git a/test/AsmResolver.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.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs new file mode 100644 index 000000000..7d3f91e09 --- /dev/null +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.DotNet.TestCases.Methods; +using AsmResolver.PE.DotNet.Cil; +using Xunit; + +namespace AsmResolver.DotNet.Dynamic.Tests +{ + public class DynamicMethodDefinitionTest + { + [Fact] + public void ReadDynamicMethod() + { + var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); + var generatedDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); + var dynamicMethodDef = new DynamicMethodDefinition(module, generatedDynamicMethod); + + Assert.NotNull(dynamicMethodDef); + Assert.NotEmpty(dynamicMethodDef.CilMethodBody!.Instructions); + Assert.Equal(new[] + { + CilCode.Ldarg_0, + CilCode.Stloc_0, + CilCode.Ldloc_0, + CilCode.Call, + CilCode.Ldarg_1, + CilCode.Ret + }, dynamicMethodDef.CilMethodBody.Instructions.Select(q => q.OpCode.Code)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + module.CorLibTypeFactory.Int32 + }, dynamicMethodDef.Parameters.Select(q => q.ParameterType)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + }, dynamicMethodDef.CilMethodBody.LocalVariables.Select(v => v.VariableType)); + } + + [Fact] + public void RtDynamicMethod() + { + var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); + + var generatedDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); + object rtDynamicMethod = generatedDynamicMethod + .GetType() + .GetField("m_dynMethod", (BindingFlags) (-1))? + .GetValue(generatedDynamicMethod); + var dynamicMethod = new DynamicMethodDefinition(module, rtDynamicMethod!); + + Assert.NotNull(dynamicMethod); + Assert.NotEmpty(dynamicMethod.CilMethodBody!.Instructions); + Assert.Equal(new[] + { + CilCode.Ldarg_0, + CilCode.Stloc_0, + CilCode.Ldloc_0, + CilCode.Call, + CilCode.Ldarg_1, + CilCode.Ret + }, dynamicMethod.CilMethodBody.Instructions.Select(q => q.OpCode.Code)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + module.CorLibTypeFactory.Int32 + }, dynamicMethod.Parameters.Select(q => q.ParameterType)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + }, dynamicMethod.CilMethodBody.LocalVariables.Select(v => v.VariableType)); + } + } +} diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 5a428afc5..48c9e7ed2 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs b/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs index 583238a58..44daafcce 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using AsmResolver.DotNet.Builder; using AsmResolver.PE; using Xunit; @@ -42,5 +43,41 @@ public void ExecutableImportDirectoryShouldContainMsCoreeCorDllMain() relocation.Location.CanRead && relocation.Location.CreateReader().ReadUInt32() == image.ImageBase + symbol.AddressTableEntry!.Rva); } + + [Fact] + public void ConstructPEImageFromNewModuleWithNoPreservation() + { + var module = new ModuleDefinition("Module"); + var result = module.ToPEImage(); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromNewModuleWithPreservation() + { + var module = new ModuleDefinition("Module"); + var result = module.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromExistingModuleWithNoPreservation() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var result = module.ToPEImage(); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromExistingModuleWithPreservation() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var result = module.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs index ebb5bc237..fa233b1e8 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs @@ -99,5 +99,19 @@ public void PreserveDuplicatedTypeRefs() references.Select(r => r.MetadataToken).ToHashSet(), newObjectReferences.Select(r => r.MetadataToken).ToHashSet()); } + + [Fact] + public void PreserveNestedTypeRefOrdering() + { + // https://github.com/Washi1337/AsmResolver/issues/329 + + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_UnusualNestedTypeRefOrder); + var originalTypeRefs = GetMembers(module, TableIndex.TypeRef); + + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveTypeReferenceIndices); + var newTypeRefs = GetMembers(newModule, TableIndex.TypeRef); + + Assert.Equal(originalTypeRefs, newTypeRefs.Take(originalTypeRefs.Count), Comparer); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs index cf0bddfb0..edb113ca1 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs @@ -15,7 +15,7 @@ public void ReadUncompressedStringContents() { var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); var file = manifest.Files.First(f => f.Type == BundleFileType.RuntimeConfigJson); - string contents = Encoding.UTF8.GetString(file.GetData()); + string contents = Encoding.UTF8.GetString(file.GetData()).Replace("\r", ""); Assert.Equal(@"{ ""runtimeOptions"": { @@ -28,7 +28,7 @@ public void ReadUncompressedStringContents() ""System.Reflection.Metadata.MetadataUpdater.IsSupported"": false } } -}", contents); +}".Replace("\r", ""), contents); } [Fact] diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index fadcc2b51..9a8e72d2e 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; @@ -329,5 +330,54 @@ 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())); + + } + + [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())); + + } + } -} \ 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"); diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index 5052f8346..a41eae6e8 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using AsmResolver.DotNet.Code.Native; using AsmResolver.DotNet.Signatures; using AsmResolver.PE; @@ -19,6 +20,8 @@ namespace AsmResolver.DotNet.Tests.Code.Native { public class NativeMethodBodyTest : IClassFixture { + private const string NonWindowsPlatform = "Test produces a mixed mode assembly which is not supported on non-Windows platforms."; + private TemporaryDirectoryFixture _fixture; public NativeMethodBodyTest(TemporaryDirectoryFixture fixture) @@ -254,7 +257,7 @@ public void ReadNativeMethodShouldResultInReferenceWithRightContents() Assert.Equal(body.Code, newBuffer); } - [Theory] + [SkippableTheory] [InlineData( true, new byte[] {0xB8, 0x00, 0x00, 0x00, 0x00}, // mov eax, message @@ -267,6 +270,8 @@ public void ReadNativeMethodShouldResultInReferenceWithRightContents() 11u)] public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint fixupOffset, AddressFixupType fixupType, uint symbolOffset) { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), NonWindowsPlatform); + // Create native body. var code = new List(movInstruction); code.AddRange(new byte[] diff --git a/test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs deleted file mode 100644 index f8f203513..000000000 --- a/test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using AsmResolver.DotNet.Signatures.Types; -using AsmResolver.DotNet.TestCases.Methods; -using AsmResolver.PE.DotNet.Cil; -using Xunit; - -namespace AsmResolver.DotNet.Tests -{ - public class DynamicMethodDefinitionTest - { - [Fact] - public void ReadDynamicMethod() - { - var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); - - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - var dynamicMethodDefinition = new DynamicMethodDefinition(module, generateDynamicMethod); - - Assert.NotNull(dynamicMethodDefinition); - - Assert.NotEmpty(dynamicMethodDefinition.CilMethodBody.Instructions); - - Assert.Equal(dynamicMethodDefinition.CilMethodBody.Instructions.Select(q=>q.OpCode),new [] - { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - Assert.Equal(dynamicMethodDefinition.Parameters.Select(q=>q.ParameterType),new TypeSignature[] - { - module.CorLibTypeFactory.String, - module.CorLibTypeFactory.Int32 - }); - } - - [Fact] - public void RtDynamicMethod() - { - var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); - - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - var rtDynamicMethod = generateDynamicMethod.GetType().GetField("m_dynMethod", (BindingFlags) (-1))?.GetValue(generateDynamicMethod); - - var dynamicMethodDefinition = new DynamicMethodDefinition(module, rtDynamicMethod); - - Assert.NotNull(dynamicMethodDefinition); - - Assert.NotEmpty(dynamicMethodDefinition.CilMethodBody.Instructions); - - Assert.Equal(dynamicMethodDefinition.CilMethodBody.Instructions.Select(q=>q.OpCode),new [] - { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - Assert.Equal(dynamicMethodDefinition.Parameters.Select(q=>q.ParameterType),new TypeSignature[] - { - module.CorLibTypeFactory.String, - module.CorLibTypeFactory.Int32 - }); - } - } -} \ No newline at end of file diff --git a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs index d3bc6ecac..48de4c707 100644 --- a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs @@ -224,6 +224,29 @@ public void MaliciousExportedTypeLoop() Assert.Null(reference.Resolve()); } + [Fact] + public void ResolveToOlderNetVersion() + { + // https://github.com/Washi1337/AsmResolver/issues/321 + + var mainApp = ModuleDefinition.FromBytes(Properties.Resources.DifferentNetVersion_MainApp); + var library = ModuleDefinition.FromBytes(Properties.Resources.DifferentNetVersion_Library); + + mainApp.MetadataResolver.AssemblyResolver.AddToCache(library.Assembly!, library.Assembly!); + + var definition = library + .TopLevelTypes.First(t => t.Name == "MyClass") + .Methods.First(m => m.Name == "ThrowMe"); + + var reference = (IMethodDescriptor) mainApp.ManagedEntrypointMethod!.CilMethodBody!.Instructions.First( + i => i.OpCode == CilOpCodes.Callvirt && ((IMethodDescriptor) i.Operand)?.Name == "ThrowMe") + .Operand!; + + var resolved = reference.Resolve(); + Assert.NotNull(resolved); + Assert.Equal(definition, resolved); + } + [Fact] public void ResolveMethodWithoutHideBySig() { 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); } diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index c2e4010ea..fbf27fbcb 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -164,6 +164,13 @@ public static byte[] HelloWorld_SingleFile_V6_WithResources { } } + public static byte[] HelloWorld_UnusualNestedTypeRefOrder { + get { + object obj = ResourceManager.GetObject("HelloWorld_UnusualNestedTypeRefOrder", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] Assembly1_Forwarder { get { object obj = ResourceManager.GetObject("Assembly1_Forwarder", resourceCulture); @@ -227,6 +234,20 @@ public static byte[] FieldRvaTest { } } + public static byte[] DifferentNetVersion_MainApp { + get { + object obj = ResourceManager.GetObject("DifferentNetVersion_MainApp", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] DifferentNetVersion_Library { + get { + object obj = ResourceManager.GetObject("DifferentNetVersion_Library", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] CallManagedExport_X86 { get { object obj = ResourceManager.GetObject("CallManagedExport_X86", resourceCulture); diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index 5e722a4a9..e919e0c39 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -69,6 +69,9 @@ ..\Resources\HelloWorld.SingleFile.v6.WithResources.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.UnusualNestedTypeRefOrder.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\Assembly1_Forwarder.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -96,6 +99,12 @@ ..\Resources\FieldRvaTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\DifferentNetVersion.MainApp.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\DifferentNetVersion.Library.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\CallManagedExport.x86.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.Library.dll b/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.Library.dll new file mode 100644 index 000000000..b1e05392b Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.Library.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.MainApp.dll b/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.MainApp.dll new file mode 100644 index 000000000..e484ca0fd Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.MainApp.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.UnusualNestedTypeRefOrder.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.UnusualNestedTypeRefOrder.exe new file mode 100644 index 000000000..c70040376 Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.UnusualNestedTypeRefOrder.exe differ diff --git a/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs b/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs index d763cc1c4..ee2e8c2e9 100644 --- a/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs @@ -12,7 +12,7 @@ namespace AsmResolver.DotNet.Tests { public class TypeSpecificationTest { - + [Fact] public void ReadGenericTypeInstantiation() { @@ -33,17 +33,17 @@ public void ReadGenericTypeInstantiation() [Fact] public void PersistentGenericTypeInstantiation() - { + { var module = ModuleDefinition.FromFile(typeof(GenericsTestClass).Assembly.Location); - + using var tempStream = new MemoryStream(); module.Write(tempStream); - + module = ModuleDefinition.FromBytes(tempStream.ToArray()); var fieldType = module - .TopLevelTypes.First(t => t.Name == nameof(GenericsTestClass)) - .Fields.First(f => f.Name == nameof(GenericsTestClass.GenericField)) - .Signature.FieldType; + .TopLevelTypes.First(t => t.Name == nameof(GenericsTestClass))! + .Fields.First(f => f.Name == nameof(GenericsTestClass.GenericField))! + .Signature!.FieldType; Assert.IsAssignableFrom(fieldType); var genericType = (GenericInstanceTypeSignature) fieldType; @@ -53,7 +53,7 @@ public void PersistentGenericTypeInstantiation() "System.String", "System.Int32", "System.Object" }, genericType.TypeArguments.Select(a => a.FullName)); } - + [Fact] public void IllegalTypeSpecInTypeDefOrRef() { @@ -61,7 +61,7 @@ public void IllegalTypeSpecInTypeDefOrRef() var typeSpec = (TypeSpecification) module.LookupMember(new MetadataToken(TableIndex.TypeSpec, 1)); Assert.NotNull(typeSpec); } - + [Fact] public void MaliciousTypeSpecLoop() { @@ -70,6 +70,6 @@ public void MaliciousTypeSpecLoop() var typeSpec = (TypeSpecification) module.LookupMember(new MetadataToken(TableIndex.TypeSpec, 1)); Assert.NotNull(typeSpec.Signature); } - + } -} \ No newline at end of file +} diff --git a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj index ba56b3821..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,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index 2e5602e90..b9f309112 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -16,9 +16,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/DotNet/Builder/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 diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs index ed16611f3..e7f8dc2f5 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); @@ -113,7 +114,7 @@ public void PreserveMetadataNoChange() Assert.Equal(metadata.Flags, newMetadata.Flags); Assert.Equal(metadata.Streams.Count, newMetadata.Streams.Count); - for (int i = 0; i < metadata.Streams.Count; i++) + Assert.All(Enumerable.Range(0, metadata.Streams.Count), i => { var oldStream = metadata.Streams[i]; var newStream = newMetadata.Streams[i]; @@ -122,8 +123,7 @@ public void PreserveMetadataNoChange() var oldData = oldStream.CreateReader().ReadToEnd(); var newData = newStream.CreateReader().ReadToEnd(); Assert.Equal(oldData, newData); - - } + }); } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs new file mode 100644 index 000000000..6927f3554 --- /dev/null +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs @@ -0,0 +1,60 @@ +using System.IO; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Pdb; +using AsmResolver.PE.DotNet.Metadata.Tables; +using Xunit; + +namespace AsmResolver.PE.Tests.DotNet.Metadata +{ + public class PdbStreamTest + { + private static IMetadata GetMetadata(bool rebuild) + { + var metadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + if (rebuild) + { + using var stream = new MemoryStream(); + metadata.Write(new BinaryStreamWriter(stream)); + metadata = PE.DotNet.Metadata.Metadata.FromBytes(stream.ToArray()); + } + + return metadata; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Id(bool rebuild) + { + var metadata = GetMetadata(rebuild); + Assert.Equal(new byte[] + { + 0x95, 0x26, 0xB5, 0xAC, 0xA7, 0xB, 0xB1, 0x4D, 0x9B, 0xF3, + 0xCD, 0x31, 0x73, 0xB, 0xE9, 0x64, 0xBE, 0xFE, 0x11, 0xFC + }, metadata.GetStream().Id); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TypeSystemRowCounts(bool rebuild) + { + var metadata = GetMetadata(rebuild); + var pdbStream = metadata.GetStream(); + var tablesStream = metadata.GetStream(); + + Assert.Equal(new uint[] + { + 1, 17, 2, 0, 0, 0, 5, 0, 3, 0, 16, 0, 12, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 1, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, pdbStream.TypeSystemRowCounts); + + Assert.True(tablesStream.HasExternalRowCounts); + Assert.Equal( + tablesStream.ExternalRowCounts.Take((int) TableIndex.MaxTypeSystemTableIndex), + pdbStream.TypeSystemRowCounts.Take((int) TableIndex.MaxTypeSystemTableIndex)); + } + } +} diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/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 @@ public static void AssertWriteThenReadIsSame(TRow expected, 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..e207837f3 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs @@ -1,5 +1,8 @@ using System.IO; +using System.Linq; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.File; using Xunit; @@ -34,10 +37,72 @@ public void PreserveTableStreamNoChange() var peImage = PEImage.FromFile(peFile); var tablesStream = peImage.DotNetDirectory!.Metadata!.GetStream(); + AssertEquivalentAfterRebuild(tablesStream); + } + + [Fact] + public void SmallExternalIndicesShouldHaveSmallIndicesInTablesStream() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(IndexSize.Short, stream.GetIndexEncoder(CodedIndex.HasCustomAttribute).IndexSize); + } + + [Fact] + public void LargeExternalIndicesShouldHaveLargeIndicesInTablesStream() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.LargeIndicesPdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(IndexSize.Long, stream.GetIndexEncoder(CodedIndex.HasCustomAttribute).IndexSize); + } + + [Fact] + public void PreservePdbTableStreamWithSmallExternalIndicesNoChange() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + AssertEquivalentAfterRebuild(pdbMetadata.GetStream()); + } + + [Fact] + public void PreservePdbTableStreamWithLargeExternalIndicesNoChange() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.LargeIndicesPdb); + AssertEquivalentAfterRebuild(pdbMetadata.GetStream()); + } + + [Fact] + public void GetImpliedTableRowCountFromNonPdbMetadataShouldGetLocalRowCount() + { + var peImage = PEImage.FromBytes(Properties.Resources.HelloWorld); + var stream = peImage.DotNetDirectory!.Metadata!.GetStream(); + Assert.Equal((uint) stream.GetTable(TableIndex.TypeDef).Count, stream.GetTableRowCount(TableIndex.TypeDef)); + } + + [Fact] + public void GetImpliedTableRowCountFromPdbMetadataShouldGetExternalRowCount() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(2u, stream.GetTableRowCount(TableIndex.TypeDef)); + Assert.Equal(0u ,(uint) stream.GetTable(TableIndex.TypeDef).Count); + } + + private static void AssertEquivalentAfterRebuild(TablesStream tablesStream) + { using var tempStream = new MemoryStream(); tablesStream.Write(new BinaryStreamWriter(tempStream)); - var newTablesStream = new SerializedTableStream(new PEReaderContext(new PEFile()), tablesStream.Name, tempStream.ToArray()); + var context = new MetadataReaderContext(VirtualAddressFactory.Instance); + var newTablesStream = new SerializedTableStream(context, tablesStream.Name, tempStream.ToArray()); + + var metadata = new PE.DotNet.Metadata.Metadata(); + if (tablesStream.HasExternalRowCounts) + { + var pdbStream = new PdbStream(); + pdbStream.UpdateRowCounts(tablesStream.ExternalRowCounts); + metadata.Streams.Add(pdbStream); + } + newTablesStream.Initialize(metadata); Assert.Equal(tablesStream.Reserved, newTablesStream.Reserved); Assert.Equal(tablesStream.MajorVersion, newTablesStream.MajorVersion); @@ -46,15 +111,19 @@ public void PreserveTableStreamNoChange() Assert.Equal(tablesStream.Log2LargestRid, newTablesStream.Log2LargestRid); Assert.Equal(tablesStream.ExtraData, newTablesStream.ExtraData); - for (TableIndex i = 0; i <= TableIndex.GenericParamConstraint; i++) + Assert.All(Enumerable.Range(0, (int) TableIndex.Max), i => { - var oldTable = tablesStream.GetTable(i); - var newTable = newTablesStream.GetTable(i); + var tableIndex = (TableIndex) i; + if (!tableIndex.IsValidTableIndex()) + return; + var oldTable = tablesStream.GetTable(tableIndex); + var newTable = newTablesStream.GetTable(tableIndex); + + Assert.Equal(oldTable.IsSorted, newTable.IsSorted); Assert.Equal(oldTable.Count, newTable.Count); - for (int j = 0; j < oldTable.Count; j++) - Assert.Equal(oldTable[j], newTable[j]); - } + Assert.All(Enumerable.Range(0, oldTable.Count), j => Assert.Equal(oldTable[j], newTable[j])); + }); } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 388c1419d..df4387877 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -94,6 +94,13 @@ public static byte[] HelloWorld_TablesStream_ExtraData { } } + public static byte[] HelloWorldPortablePdb { + get { + object obj = ResourceManager.GetObject("HelloWorldPortablePdb", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] SimpleDll { get { object obj = ResourceManager.GetObject("SimpleDll", resourceCulture); @@ -122,6 +129,13 @@ public static byte[] TheAnswer_NetCore { } } + public static byte[] TheAnswerPortablePdb { + get { + object obj = ResourceManager.GetObject("TheAnswerPortablePdb", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] SEHSamples { get { object obj = ResourceManager.GetObject("SEHSamples", resourceCulture); @@ -156,5 +170,12 @@ public static byte[] TlsTest_x64 { return ((byte[])(obj)); } } + + public static byte[] LargeIndicesPdb { + get { + object obj = ResourceManager.GetObject("LargeIndicesPdb", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index e9a93ecd7..50e2489ac 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 @@ -51,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 @@ -66,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 + 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 000000000..aba7e1f78 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.pdb differ diff --git a/test/AsmResolver.PE.Tests/Resources/LargeIndicesPdb.pdb b/test/AsmResolver.PE.Tests/Resources/LargeIndicesPdb.pdb new file mode 100644 index 000000000..d3107bf70 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/LargeIndicesPdb.pdb differ diff --git a/test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb b/test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb new file mode 100644 index 000000000..479dfee18 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb differ diff --git a/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs b/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs index 72e200b43..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, diff --git a/test/AsmResolver.PE.Tests/VirtualAddress.cs b/test/AsmResolver.PE.Tests/VirtualAddress.cs deleted file mode 100644 index b9424e496..000000000 --- a/test/AsmResolver.PE.Tests/VirtualAddress.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using AsmResolver.IO; - -namespace AsmResolver.PE.Tests -{ - public readonly struct VirtualAddress : ISegmentReference - { - public VirtualAddress(uint rva) - { - Rva = rva; - } - - ulong IOffsetProvider.Offset => Rva; - - public uint Rva - { - get; - } - - bool IOffsetProvider.CanUpdateOffsets => false; - - void IOffsetProvider.UpdateOffsets(ulong newOffset, uint newRva) => throw new InvalidOperationException(); - - public bool CanRead => false; - - bool ISegmentReference.IsBounded => false; - - BinaryStreamReader ISegmentReference.CreateReader() => throw new InvalidOperationException(); - - public ISegment? GetSegment() => throw new InvalidOperationException(); - } -} diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index c6ae10b54..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,8 +8,8 @@ - - + + 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..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,8 +8,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs new file mode 100644 index 000000000..a059b14b4 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs @@ -0,0 +1,23 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ArgumentListLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ArgumentListLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadMultipleTypes() + { + var list = (ArgumentListLeaf) _fixture.SimplePdb.GetLeafRecord(0x2391); + Assert.IsAssignableFrom(list.Types[0]); + Assert.IsAssignableFrom(list.Types[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs new file mode 100644 index 000000000..b33c90b37 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs @@ -0,0 +1,42 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ArrayTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ArrayTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadElementType() + { + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(type.ElementType).Kind); + } + + [Fact] + public void ReadIndexType() + { + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(SimpleTypeKind.UInt32Long, Assert.IsAssignableFrom(type.IndexType).Kind); + } + + [Fact] + public void ReadLength() + { + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(4u, type.Length); + } + + [Fact] + public void ReadEmptyName() + { + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.True(Utf8String.IsNullOrEmpty(type.Name)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs new file mode 100644 index 000000000..34902397f --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs @@ -0,0 +1,35 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class BitFieldTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public BitFieldTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadBaseType() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(SimpleTypeKind.UInt32Long, Assert.IsAssignableFrom(type.Type).Kind); + } + + [Fact] + public void ReadPosition() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(2, type.Position); + } + + [Fact] + public void ReadLength() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(30, type.Length); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs new file mode 100644 index 000000000..e51e80d1e --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs @@ -0,0 +1,61 @@ +using System; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ClassTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ClassTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Theory] + [InlineData(CodeViewLeafKind.Class)] + [InlineData(CodeViewLeafKind.Structure)] + [InlineData(CodeViewLeafKind.Interface)] + public void CreateNewValidType(CodeViewLeafKind kind) + { + var type = new ClassTypeRecord(kind, "MyType", "MyUniqueType", 4, StructureAttributes.FwdRef, null); + Assert.Equal(kind, type.LeafKind); + Assert.Equal("MyType", type.Name); + Assert.Equal("MyUniqueType", type.UniqueName); + Assert.Equal(4u, type.Size); + Assert.Equal(StructureAttributes.FwdRef, type.StructureAttributes); + Assert.Null(type.BaseType); + } + + [Fact] + public void CreateNonValidType() + { + Assert.Throws(() => new ClassTypeRecord( + CodeViewLeafKind.Char, + "Invalid", + "Invalid", + 4, + StructureAttributes.FwdRef, + null)); + } + + [Fact] + public void ReadFieldList() + { + var type = (ClassTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x101b); + Assert.NotNull(type.Fields); + } + + [Fact] + public void ReadVTableShape() + { + var type = (ClassTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x239f); + Assert.NotNull(type.VTableShape); + Assert.Equal(new[] + { + VTableShapeEntry.Near32, + VTableShapeEntry.Near32, + }, type.VTableShape.Entries); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs new file mode 100644 index 000000000..5bff46dd0 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs @@ -0,0 +1,37 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class EnumTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public EnumTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void FieldList() + { + var leaf = _fixture.SimplePdb.GetLeafRecord(0x1009); + var fields = Assert.IsAssignableFrom(leaf).Fields.Entries.Cast().ToArray(); + var names = new[] + { + "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", + "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST", + "DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32", + }; + Assert.Equal(names[0], fields[0].Name); + Assert.Equal(names[1], fields[1].Name); + Assert.Equal(names[2], fields[2].Name); + Assert.Equal(names[3], fields[3].Name); + Assert.Equal(names[4], fields[4].Name); + Assert.Equal(names[5], fields[5].Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs new file mode 100644 index 000000000..cd067940b --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs @@ -0,0 +1,130 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewFieldAttributes; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class FieldListLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public FieldListLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadEnumerateList() + { + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1008); + var enumerates = list.Entries + .Cast() + .Select(f => (f.Attributes, f.Name.Value, f.Value)) + .ToArray(); + + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", 0u), enumerates[0]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", 1u), enumerates[1]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED", 2u), enumerates[2]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST", 2u), enumerates[3]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST", 3u), enumerates[4]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32", '\xff'), enumerates[5]); + } + + [Fact] + public void ReadInstanceDataMemberList() + { + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1017); + var enumerates = list.Entries + .Cast() + .Select(f => (f.Attributes, f.Name.Value, f.Offset)) + .ToArray(); + + Assert.Equal((Public, "cbSize", 0ul), enumerates[0]); + Assert.Equal((Public, "fMask", 4ul), enumerates[1]); + Assert.Equal((Public, "fType", 8ul), enumerates[2]); + Assert.Equal((Public, "fState", 12ul), enumerates[3]); + Assert.Equal((Public, "wID", 16ul), enumerates[4]); + Assert.Equal((Public, "hSubMenu", 20ul), enumerates[5]); + Assert.Equal((Public, "hbmpChecked", 24ul), enumerates[6]); + Assert.Equal((Public, "hbmpUnchecked", 28ul), enumerates[7]); + Assert.Equal((Public, "dwItemData", 32ul), enumerates[8]); + Assert.Equal((Public, "dwTypeData", 36ul), enumerates[9]); + Assert.Equal((Public, "cch", 40ul), enumerates[10]); + Assert.Equal((Public, "hbmpItem", 44ul), enumerates[11]); + } + + [Fact] + public void ReadMethodsAndBaseClass() + { + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x239d); + + Assert.Equal("std::exception", Assert.IsAssignableFrom( + Assert.IsAssignableFrom(list.Entries[0]).Type).Name); + Assert.Equal("bad_cast", Assert.IsAssignableFrom(list.Entries[1]).Name); + Assert.Equal("__construct_from_string_literal", Assert.IsAssignableFrom(list.Entries[2]).Name); + Assert.Equal("~bad_cast", Assert.IsAssignableFrom(list.Entries[3]).Name); + Assert.Equal("operator=", Assert.IsAssignableFrom(list.Entries[4]).Name); + Assert.Equal("__local_vftable_ctor_closure", Assert.IsAssignableFrom(list.Entries[5]).Name); + Assert.Equal("__vecDelDtor", Assert.IsAssignableFrom(list.Entries[6]).Name); + } + + [Fact] + public void ReadNestedTypes() + { + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1854); + + Assert.Equal("_LDT_ENTRY::::", + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[0]).Type).Name); + Assert.Equal("_LDT_ENTRY::::", + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[2]).Type).Name); + } + + [Fact] + public void ReadVirtualBaseClass() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1347); + var baseClass = Assert.IsAssignableFrom(list.Entries[0]); + + Assert.Equal("std::basic_ios >", + Assert.IsAssignableFrom(baseClass.Type).Name); + Assert.True(Assert.IsAssignableFrom(baseClass.PointerType).IsNear64); + Assert.False(baseClass.IsIndirect); + Assert.Equal(0ul, baseClass.PointerOffset); + Assert.Equal(1ul, baseClass.TableOffset); + } + + [Fact] + public void ReadIndirectVirtualBaseClass() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1e97); + var baseClass = Assert.IsAssignableFrom(list.Entries[2]); + + Assert.Equal("std::basic_ios >", + Assert.IsAssignableFrom(baseClass.Type).Name); + Assert.True(Assert.IsAssignableFrom(baseClass.PointerType).IsNear64); + Assert.True(baseClass.IsIndirect); + Assert.Equal(0ul, baseClass.PointerOffset); + Assert.Equal(1ul, baseClass.TableOffset); + } + + [Fact] + public void ReadStaticFields() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1423); + + Assert.Equal("is_bounded", Assert.IsAssignableFrom(list.Entries[1]).Name); + Assert.Equal("is_exact", Assert.IsAssignableFrom(list.Entries[2]).Name); + Assert.Equal("is_integer", Assert.IsAssignableFrom(list.Entries[3]).Name); + Assert.Equal("is_specialized", Assert.IsAssignableFrom(list.Entries[4]).Name); + Assert.Equal("radix", Assert.IsAssignableFrom(list.Entries[5]).Name); + } + + [Fact] + public void ReadVTableField() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1215); + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[0]).PointerType); + Assert.IsAssignableFrom(list.Entries[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs new file mode 100644 index 000000000..465f27203 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs @@ -0,0 +1,44 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class MemberFunctionLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public MemberFunctionLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadReturnType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(function.ReturnType).Kind); + } + + [Fact] + public void ReadDeclaringType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.Equal("std::bad_cast", Assert.IsAssignableFrom(function.DeclaringType).Name); + } + + [Fact] + public void ReadNonNullThisType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.IsAssignableFrom(function.ThisType); + } + + [Fact] + public void ReadArgumentList() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.NotNull(function.Arguments); + Assert.IsAssignableFrom(function.Arguments!.Types[0]); + Assert.IsAssignableFrom(function.Arguments.Types[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs new file mode 100644 index 000000000..53941c44f --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs @@ -0,0 +1,33 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewFieldAttributes; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class MethodListLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public MethodListLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadNonIntroVirtualEntries() + { + var list = (MethodListLeaf) _fixture.SimplePdb.GetLeafRecord(0x2394); + var entries = list.Entries; + + Assert.Equal(new[] + { + Public | CompilerGenerated, + Public | CompilerGenerated, + Private, + Public, + }, entries.Select(e => e.Attributes)); + + Assert.All(entries, Assert.NotNull); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs new file mode 100644 index 000000000..e9a03fc40 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs @@ -0,0 +1,35 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ModifierTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ModifierTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void CreateNewType() + { + var type = new ModifierTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), ModifierAttributes.Const); + Assert.True(type.IsConst); + } + + [Fact] + public void ReadAttributes() + { + var type = (ModifierTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1011); + Assert.True(type.IsConst); + } + + [Fact] + public void ReadBaseType() + { + var type = (ModifierTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1011); + Assert.Equal(CodeViewLeafKind.Structure, type.BaseType.LeafKind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs new file mode 100644 index 000000000..854c560b6 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs @@ -0,0 +1,60 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class PointerTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PointerTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void CreateNewType() + { + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + Assert.True(type.IsConst); + Assert.Equal(4, type.Size); + } + + [Fact] + public void UpdateKind() + { + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Kind = PointerAttributes.Near32; + Assert.Equal(PointerAttributes.Near32, type.Kind); + } + + [Fact] + public void UpdateMode() + { + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Mode = PointerAttributes.LValueReference; + Assert.Equal(PointerAttributes.LValueReference, type.Mode); + } + + [Fact] + public void UpdateSize() + { + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Size = 8; + Assert.Equal(8, type.Size); + } + + [Fact] + public void ReadAttributes() + { + var type = (PointerTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1012); + Assert.True(type.IsNear32); + } + + [Fact] + public void ReadBaseType() + { + var type = (PointerTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1012); + Assert.Equal(CodeViewLeafKind.Modifier, type.BaseType.LeafKind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs new file mode 100644 index 000000000..4067d6f7d --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs @@ -0,0 +1,36 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ProcedureTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ProcedureTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadReturnType() + { + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(procedure.ReturnType).Kind); + } + + [Fact] + public void ReadCallingConvention() + { + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.Equal(CodeViewCallingConvention.NearStd, procedure.CallingConvention); + } + + [Fact] + public void ReadArguments() + { + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.NotNull(procedure.Arguments); + Assert.Equal(2, procedure.Arguments!.Types.Count); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs new file mode 100644 index 000000000..ac2a784c7 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs @@ -0,0 +1,17 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class SimpleTypeRecordTest +{ + [Theory] + [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] + [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] + public void TypeIndexParsing(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) + { + var type = new SimpleTypeRecord(typeIndex); + Assert.Equal(kind, type.Kind); + Assert.Equal(mode, type.Mode); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs new file mode 100644 index 000000000..75f7c747c --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs @@ -0,0 +1,47 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class UnionTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public UnionTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadSize() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal(4ul, type.Size); + } + + [Fact] + public void ReadName() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal("_LDT_ENTRY::", type.Name); + } + + [Fact] + public void ReadUniqueName() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal(".?AT@_LDT_ENTRY@@", type.UniqueName); + } + + [Fact] + public void ReadFieldList() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + var fields = type.Fields!; + Assert.NotNull(fields); + Assert.IsAssignableFrom(fields.Entries[0]); + Assert.IsAssignableFrom(fields.Entries[1]); + Assert.IsAssignableFrom(fields.Entries[2]); + Assert.IsAssignableFrom(fields.Entries[3]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs new file mode 100644 index 000000000..929183c76 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs @@ -0,0 +1,23 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class VTableShapeLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public VTableShapeLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Theory] + [InlineData(0x2416, new[] { VTableShapeEntry.Near })] + [InlineData(0x239e, new[] { VTableShapeEntry.Near32,VTableShapeEntry.Near32 })] + public void ReadEntries(uint typeIndex, VTableShapeEntry[] expectedEntries) + { + var shape = (VTableShapeLeaf) _fixture.SimplePdb.GetLeafRecord(typeIndex); + Assert.Equal(expectedEntries, shape.Entries); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs new file mode 100644 index 000000000..889913627 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -0,0 +1,213 @@ +using System; +using System.IO; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; +using AsmResolver.Symbols.Pdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Metadata.Dbi; + +public class DbiStreamTest +{ + private DbiStream GetDbiStream(bool rebuild) + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + if (rebuild) + { + using var stream = new MemoryStream(); + dbiStream.Write(new BinaryStreamWriter(stream)); + dbiStream = DbiStream.FromReader(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); + Assert.Equal(14, dbiStream.BuildMajorVersion); + Assert.Equal(29, dbiStream.BuildMinorVersion); + Assert.True(dbiStream.IsNewVersionFormat); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ModuleNames(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(new[] + { + "* CIL *", + "C:\\Users\\Admin\\source\\repos\\AsmResolver\\test\\TestBinaries\\Native\\SimpleDll\\Release\\dllmain.obj", + "C:\\Users\\Admin\\source\\repos\\AsmResolver\\test\\TestBinaries\\Native\\SimpleDll\\Release\\pch.obj", + "* Linker Generated Manifest RES *", + "Import:KERNEL32.dll", + "KERNEL32.dll", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\sehprolg4.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\gs_cookie.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\gs_report.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\gs_support.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\guard_support.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\loadcfg.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\dyn_tls_init.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\ucrt_detection.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\cpu_disp.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\chandler4gs.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\secchk.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\argv_mode.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\default_local_stdio_options.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\tncleanup.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\dll_dllmain.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\initializers.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\utility.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\ucrt_stubs.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\utility_desktop.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\initsect.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\x86_exception_filter.obj", + "VCRUNTIME140.dll", + "Import:VCRUNTIME140.dll", + "Import:api-ms-win-crt-runtime-l1-1-0.dll", + "api-ms-win-crt-runtime-l1-1-0.dll", + "* Linker *", + }, dbiStream.Modules.Select(m => m.ModuleName?.Value)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SectionContributions(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(new (ushort, uint)[] + { + (1, 1669053862), (16, 2162654757), (20, 1635644926), (20, 3159649454), (20, 1649652954), (20, 3877379438), + (20, 4262788820), (20, 199934614), (8, 4235719287), (8, 1374843914), (9, 4241735292), (9, 2170796787), + (19, 1300950661), (19, 3968158929), (18, 3928463356), (18, 3928463356), (18, 2109213706), (22, 1457516325), + (22, 3939645857), (22, 1393694582), (22, 546064581), (22, 1976627334), (22, 513172946), (22, 25744891), + (22, 1989765812), (22, 2066266302), (22, 3810887196), (22, 206965504), (22, 647717352), (22, 3911072265), + (22, 3290064241), (12, 3928463356), (24, 2717331243), (24, 3687876222), (25, 2318145338), (25, 2318145338), + (6, 542071654), (15, 1810708069), (10, 3974941622), (14, 1150179208), (17, 2709606169), (13, 2361171624), + (28, 0), (28, 0), (28, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), + (23, 3467414241), (23, 4079273803), (26, 1282639619), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), + (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (5, 0), (28, 0), (28, 0), (28, 0), (27, 0), (29, 0), (29, 0), + (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (30, 0), (10, 2556510175), (21, 2556510175), + (21, 2556510175), (21, 2556510175), (21, 2556510175), (21, 2556510175), (21, 2556510175), (21, 2556510175), + (21, 2556510175), (20, 2556510175), (8, 4117779887), (31, 0), (11, 525614319), (31, 0), (31, 0), (31, 0), + (31, 0), (31, 0), (25, 2556510175), (25, 2556510175), (25, 2556510175), (25, 2556510175), (20, 3906165615), + (20, 1185345766), (20, 407658226), (22, 2869884627), (27, 0), (30, 0), (5, 0), (27, 0), (4, 0), (4, 0), + (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (5, 0), (28, 0), (28, 0), (28, 0), + (27, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (30, 0), (28, 0), (28, 0), + (28, 0), (27, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (30, 0), (4, 0), + (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (5, 0), (7, 4096381681), + (22, 454268333), (14, 1927129959), (23, 1927129959), (20, 0), (8, 0), (19, 0), (18, 0), (18, 0), (22, 0), + (24, 0), (10, 0), (14, 0), (2, 0), (31, 0), (3, 0), (3, 0) + }, dbiStream.SectionContributions.Select(x => (x.ModuleIndex, x.DataCrc))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SectionMaps(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(new (ushort, ushort, ushort, ushort, ushort, ushort, uint, uint)[] + { + (0x010d, 0x0000, 0x0000, 0x0001, 0xffff, 0xffff, 0x00000000, 0x00000ce8), + (0x0109, 0x0000, 0x0000, 0x0002, 0xffff, 0xffff, 0x00000000, 0x00000834), + (0x010b, 0x0000, 0x0000, 0x0003, 0xffff, 0xffff, 0x00000000, 0x00000394), + (0x0109, 0x0000, 0x0000, 0x0004, 0xffff, 0xffff, 0x00000000, 0x000000f8), + (0x0109, 0x0000, 0x0000, 0x0005, 0xffff, 0xffff, 0x00000000, 0x0000013c), + (0x0208, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0x00000000, 0xffffffff), + }, + dbiStream.SectionMaps.Select(m => ((ushort) + m.Attributes, m.LogicalOverlayNumber, m.Group, m.Frame, + m.SectionName, m.ClassName, m.Offset, m.SectionLength))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SourceFiles(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + string[][] firstThreeActualFileLists = dbiStream.SourceFiles + .Take(3) + .Select(x => x + .Select(y => y.ToString()) + .ToArray() + ).ToArray(); + + Assert.Equal(new[] + { + Array.Empty(), + new[] + { + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\pch.h", + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\dllmain.cpp", + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\Release\SimpleDll.pch", + }, + new[] + { + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\winuser.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\basetsd.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\winbase.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\stralign.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\guiddef.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\winerror.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\corecrt_wstring.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\processthreadsapi.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\winnt.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\ctype.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\string.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\corecrt_memory.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\memoryapi.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\corecrt_memcpy_s.h", + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\Release\SimpleDll.pch", + } + }, + firstThreeActualFileLists); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ExtraDebugIndices(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(new ushort[] + { + 0x7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB, 0xFFFF, 0xFFFF, 0xFFFF, 0xD, 0xFFFF + }, dbiStream.ExtraStreamIndices); + } + + [Fact] + public void SizeCalculation() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var infoStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + uint calculatedSize = infoStream.GetPhysicalSize(); + + using var stream = new MemoryStream(); + infoStream.Write(new BinaryStreamWriter(stream)); + + Assert.Equal(stream.Length, calculatedSize); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs new file mode 100644 index 000000000..03a19f01b --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Metadata.Info; +using AsmResolver.Symbols.Pdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Metadata.Info; + +public class InfoStreamTest +{ + private static InfoStream GetInfoStream(bool rebuild) + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var infoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); + + if (rebuild) + { + using var stream = new MemoryStream(); + infoStream.Write(new BinaryStreamWriter(stream)); + infoStream = InfoStream.FromReader(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, + ["/src/headerblock"] = 46, + ["/LinkInfo"] = 5, + ["/TMCache"] = 6, + ["/names"] = 12 + }, infoStream.StreamIndices); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FeatureCodes(bool rebuild) + { + var infoStream = GetInfoStream(rebuild); + + Assert.Equal(new[] {PdbFeature.VC140}, infoStream.Features); + } + + [Fact] + public void SizeCalculation() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var infoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); + + uint calculatedSize = infoStream.GetPhysicalSize(); + + using var stream = new MemoryStream(); + infoStream.Write(new BinaryStreamWriter(stream)); + + Assert.Equal(stream.Length, calculatedSize); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/PdbHashTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/PdbHashTest.cs new file mode 100644 index 000000000..5ca3e791f --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/PdbHashTest.cs @@ -0,0 +1,18 @@ +using AsmResolver.Symbols.Pdb.Metadata; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Metadata; + +public class PdbHashTest +{ + [Theory] + [InlineData("/UDTSRCLINEUNDONE", 0x23296bb2)] + [InlineData("/src/headerblock", 0x2b237ecd)] + [InlineData("/LinkInfo", 0x282209ed)] + [InlineData("/TMCache", 0x2621d5e9)] + [InlineData("/names", 0x6d6cfc21)] + public void HashV1(string value, uint expected) + { + Assert.Equal(expected, PdbHash.ComputeV1(value)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs b/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs new file mode 100644 index 000000000..36218c414 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs @@ -0,0 +1,14 @@ +namespace AsmResolver.Symbols.Pdb.Tests; + +public class MockPdbFixture +{ + public PdbImage SimplePdb + { + get; + } = PdbImage.FromBytes(Properties.Resources.SimpleDllPdb); + + public PdbImage MyTestApplication + { + get; + } = PdbImage.FromBytes(Properties.Resources.MyTestApplication); +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs new file mode 100644 index 000000000..b1e8cfc2d --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs @@ -0,0 +1,35 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests; + +public class PdbImageTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PdbImageTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Theory] + [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] + [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] + public void SimpleTypeLookup(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) + { + var type = Assert.IsAssignableFrom(_fixture.SimplePdb.GetLeafRecord(typeIndex)); + Assert.Equal(kind, type.Kind); + Assert.Equal(mode, type.Mode); + } + + [Fact] + public void SimpleTypeLookupTwiceShouldCache() + { + var image = _fixture.SimplePdb; + + var type = image.GetLeafRecord(0x00_75); + var type2 = image.GetLeafRecord(0x00_75); + + Assert.Same(type, type2); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs 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 static System.Resources.ResourceManager ResourceManager { return resourceMan; } } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Globalization.CultureInfo Culture { get { @@ -44,12 +44,19 @@ internal static System.Globalization.CultureInfo Culture { 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/Records/ConstantTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs new file mode 100644 index 000000000..a9cfdfbf6 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs @@ -0,0 +1,34 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class ConstantTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ConstantTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + Assert.Equal("JOB_OBJECT_IO_RATE_CONTROL_STANDALONE_VOLUME", _fixture.SimplePdb.Symbols.OfType().First().Name); + } + + [Fact] + public void Type() + { + Assert.Equal(CodeViewLeafKind.Enum, _fixture.SimplePdb.Symbols.OfType().First().Type.LeafKind); + } + + [Fact] + public void Value() + { + Assert.Equal(2, _fixture.SimplePdb.Symbols.OfType().First().Value); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs new file mode 100644 index 000000000..dc16222e6 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs @@ -0,0 +1,21 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class PublicSymbolTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PublicSymbolTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + Assert.Equal("___enclave_config", _fixture.SimplePdb.Symbols.OfType().First().Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs new file mode 100644 index 000000000..bc0e8614e --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs @@ -0,0 +1,38 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class UserDefinedTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public UserDefinedTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + var udt = _fixture.SimplePdb.Symbols.OfType().First(); + Assert.Equal("UINT", udt.Name); + } + + [Fact] + public void Type() + { + var udt = _fixture.SimplePdb.Symbols.OfType().First(); + var type = Assert.IsAssignableFrom(udt.Type); + Assert.Equal(SimpleTypeKind.UInt32, type.Kind); + } + + [Fact] + public void Type2() + { + var udt = _fixture.SimplePdb.Symbols.OfType().ElementAt(1); + Assert.Equal(CodeViewLeafKind.Pointer, udt.Type.LeafKind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb b/test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb new file mode 100644 index 000000000..abb3d2833 Binary files /dev/null and b/test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb differ diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 82f9cb9c6..890b3b276 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,8 +8,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -17,6 +17,7 @@ + diff --git a/test/AsmResolver.Tests/Collections/BitListTest.cs b/test/AsmResolver.Tests/Collections/BitListTest.cs new file mode 100644 index 000000000..402f8ec2e --- /dev/null +++ b/test/AsmResolver.Tests/Collections/BitListTest.cs @@ -0,0 +1,111 @@ +using System.Linq; +using AsmResolver.Collections; +using Xunit; + +namespace AsmResolver.Tests.Collections +{ + public class BitListTest + { + [Fact] + public void Add() + { + var list = new BitList + { + true, + false, + true, + true, + false, + }; + + Assert.Equal(new[] + { + true, + false, + true, + true, + false + }, list.ToArray()); + } + + [Fact] + public void Insert() + { + var list = new BitList + { + true, + false, + true, + true, + false, + }; + + list.Insert(1, true); + + Assert.Equal(new[] + { + true, + true, + false, + true, + true, + false + }, list.ToArray()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void InsertIntoLarge(bool parity) + { + var list = new BitList(); + for (int i = 0; i < 100; i++) + list.Add(i % 2 == 0 == parity); + + list.Insert(0, !parity); + + Assert.Equal(101, list.Count); + bool[] expected = Enumerable.Range(0, 101).Select(i => i % 2 == 1 == parity).ToArray(); + Assert.Equal(expected, list.ToArray()); + } + + [Fact] + public void RemoveAt() + { + var list = new BitList + { + true, + false, + true, + true, + false, + }; + + list.RemoveAt(3); + + Assert.Equal(new[] + { + true, + false, + true, + false, + }, list.ToArray()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RemoveAtLarge(bool parity) + { + var list = new BitList(); + for (int i = 0; i < 100; i++) + list.Add(i % 2 == 0 == parity); + + list.RemoveAt(0); + + Assert.Equal(99, list.Count); + bool[] expected = Enumerable.Range(0, 99).Select(i => i % 2 == 1 == parity).ToArray(); + Assert.Equal(expected, list.ToArray()); + } + } +} diff --git a/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs b/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs new file mode 100644 index 000000000..0afb61f6d --- /dev/null +++ b/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs @@ -0,0 +1,12 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Cloning; +using System; + +namespace AsmResolver.Tests.Listeners +{ + public class CustomMemberClonerListener : MemberClonerListener + { + public override void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) => + cloned.Name = $"Method_{original.Name}"; + } +} diff --git a/test/AsmResolver.Tests/SegmentBuilderTest.cs b/test/AsmResolver.Tests/SegmentBuilderTest.cs index d64aa1c78..be2aff523 100644 --- a/test/AsmResolver.Tests/SegmentBuilderTest.cs +++ b/test/AsmResolver.Tests/SegmentBuilderTest.cs @@ -8,8 +8,6 @@ public class SegmentBuilderTest { private static byte[] ToBytes(ISegment segment) { - segment.UpdateOffsets(0, 0); - using var stream = new MemoryStream(); var writer = new BinaryStreamWriter(stream); @@ -22,7 +20,7 @@ public void EmptyNoAlignment() { var collection = new SegmentBuilder(); - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, collection.Offset); Assert.Equal(0x1000u, collection.Rva); @@ -39,7 +37,7 @@ public void SingleItemNoAlignment() var collection = new SegmentBuilder {segment}; - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment.Offset); Assert.Equal(0x1000u, segment.Rva); @@ -61,7 +59,7 @@ public void MultipleItemsNoAlignment() var collection = new SegmentBuilder {segment1, segment2, segment3}; - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment1.Offset); Assert.Equal(0x1000u, segment1.Rva); @@ -88,7 +86,7 @@ public void SingleItemAlignment() var builder = new SegmentBuilder {segment}; - builder.UpdateOffsets(0x400, 0x1000); + builder.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment.Offset); @@ -115,7 +113,7 @@ public void MultipleItemsAlignment() {segment3, 8} }; - builder.UpdateOffsets(0x400, 0x1000); + builder.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment1.Offset); Assert.Equal(0x1000u, segment1.Rva); diff --git a/test/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 +}