diff --git a/Directory.Build.props b/Directory.Build.props index f94ba5755..a1f4f6d97 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,13 +1,13 @@ - Copyright © Washi 2016-2022 + Copyright © Washi 2016-2023 https://github.com/Washi1337/AsmResolver https://github.com/Washi1337/AsmResolver/LICENSE.md https://github.com/Washi1337/AsmResolver git 10 - 5.1.0 + 5.2.0 diff --git a/appveyor.yml b/appveyor.yml index 30adc28f9..7d1c9f32d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 5.1.0-master-build.{build} + version: 5.2.0-master-build.{build} configuration: Release skip_commits: @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 5.1.0-dev-build.{build} + version: 5.2.0-dev-build.{build} configuration: Release skip_commits: diff --git a/docs/dotnet/bundles.rst b/docs/dotnet/bundles.rst index 670b9eb3d..81ef259e5 100644 --- a/docs/dotnet/bundles.rst +++ b/docs/dotnet/bundles.rst @@ -3,7 +3,7 @@ AppHost / SingleFileHost Bundles Since the release of .NET Core 3.1, it is possible to deploy .NET assemblies as a single binary. These files are executables that do not contain a traditional .NET metadata header, and run natively on the underlying operating system via a platform-specific application host bootstrapper. -AsmResolver supports extracting the embedded files from these types of binaries. Additionally, given an application host template provided by the .NET SDK, AsmResolver also supports constructing new bundles as well. All relevant code is found in the following namespace: +AsmResolver supports extracting the embedded files from these types of binaries. Additionally, given the original file or an application host template provided by the .NET SDK, AsmResolver also supports constructing new bundles as well. All relevant code is found in the following namespace: .. code-block:: csharp @@ -96,14 +96,14 @@ Constructing new bundled executable files requires a template file that AsmResol - ``/sdk//AppHostTemplate`` - ``/packs/Microsoft.NETCore.App.Host.//runtimes//native`` -Using this template file, it is then possible to write a new bundled executable file using ``WriteUsingTemplate``: +Using this template file, it is then possible to write a new bundled executable file using ``WriteUsingTemplate`` and the ``BundlerParameters::FromTemplate`` method: .. code-block:: csharp BundleManifest manifest = ... manifest.WriteUsingTemplate( @"C:\Path\To\Output\File.exe", - new BundlerParameters( + BundlerParameters.FromTemplate( appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", appBinaryPath: @"HelloWorld.dll")); @@ -117,12 +117,38 @@ For bundle executable files targeting Windows, it may be required to copy over s BundleManifest manifest = ... manifest.WriteUsingTemplate( @"C:\Path\To\Output\File.exe", - new BundlerParameters( + BundlerParameters.FromTemplate( appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", appBinaryPath: @"HelloWorld.dll", imagePathToCopyHeadersFrom: @"C:\Path\To\Original\HelloWorld.exe")); -``BundleManifest`` also defines other ```WriteUsingTemplate`` overloads taking ``byte[]``, ``IDataSource`` or ``IPEImage`` instances instead of paths. + +If you do not have access to a template file (e.g., if the SDK is not installed) but have another existing PE file that was packaged in a similar fashion, it is then possible to use this file as a template instead by extracting the bundler parameters using the ``BundlerParameters::FromExistingBundle`` method. This is in particularly useful when trying to patch existing AppHost bundles: + +.. code-block:: csharp + + string inputPath = @"C:\Path\To\Bundled\HelloWorld.exe"; + string outputPath = Path.ChangeExtension(inputPath, ".patched.exe"); + + // Read manifest. + var manifest = BundleManifest.FromFile(inputPath); + + /* ... Make changes to manifest and its files ... */ + + // Repackage bundle using existing bundle as template. + manifest.WriteUsingTemplate( + outputPath, + BundlerParameters.FromExistingBundle( + originalFile: inputPath, + appBinaryPath: mainFile.RelativePath)); + + +.. warning:: + + The ``BundlerParameters.FromExistingBundle`` method applies heuristics on the input file to determine the parameters for patching the input file. As heuristics are not perfect, this is not guaranteed to always work. + + +``BundleManifest`` and ``BundlerParameters`` also define overloads of the ``WriteUsingTemplate`` and ``FromTemplate`` / ``FromExistingBundle`` respectively, taking ``byte[]``, ``IDataSource`` or ``IPEImage`` instances instead of file paths. Managing Files diff --git a/docs/dotnet/type-signatures.rst b/docs/dotnet/type-signatures.rst index 34d5fcff1..8803693cd 100644 --- a/docs/dotnet/type-signatures.rst +++ b/docs/dotnet/type-signatures.rst @@ -11,8 +11,8 @@ All relevant classes in this document can be found in the following namespaces: using AsmResolver.DotNet.Signatures.Types; -Overview of all Type Signatures -------------------------------- +Overview +-------- Basic leaf type signatures: @@ -78,8 +78,8 @@ Corlib type signatures can also be looked up by their element type, by their ful If an invalid element type, name or type descriptor is passed on, these methods return ``null``. -TypeDefOrRefSignature ---------------------- +Class and Struct Types +---------------------- The ``TypeDefOrRefSignature`` class is used to reference types in either the ``TypeDef`` or ``TypeRef`` (and sometimes ``TypeSpec``) metadata table. @@ -101,8 +101,8 @@ Alternatively, ``CreateTypeReference`` can be used on any ``IResolutionScope``: While it is technically possible to reference a basic type such as ``System.Int32`` as a ``TypeDefOrRefSignature``, it renders the .NET module invalid by most implementations of the CLR. Always use the ``CorLibTypeSignature`` to reference basic types within your blob signatures. -GenericInstanceTypeSignature ----------------------------- +Generic Instance Types +---------------------- The ``GenericInstanceTypeSignature`` class is used to instantiate generic types with type arguments: @@ -128,8 +128,8 @@ Alternatively, a generic instance can also be generated via the ``MakeGenericTyp // listOfString now contains a reference to List. -FunctionPointerTypeSignature ----------------------------- +Function Pointer Types +---------------------- Function pointer signatures are strongly-typed pointer types used to describe addresses to functions or methods. In AsmResolver, they are represented using a ``MethodSignature``: @@ -175,8 +175,8 @@ To quickly transform any ``ITypeDescriptor`` into a ``TypeSignature``, it is pos Likewise, a ``TypeSignature`` can also be converted back to a ``ITypeDefOrRef``, which can be referenced using a metadata token, using the ``TypeSignature.ToTypeDefOrRef()`` method. -Decorating type signatures --------------------------- +Decorating Types +---------------- Type signatures can be annotated with extra properties, such as an array or pointer specifier. @@ -205,7 +205,7 @@ Below an overview of all factory shortcut methods: +===================================================================+==================================================================================================================+ | ``MakeArrayType(int dimensionCount)`` | Wraps the type in a new ``ArrayTypeSignature`` with ``dimensionCount`` zero based dimensions with no upperbound. | +-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| ``MakeArrayType(ArrayDimension[] dimensinos)`` | Wraps the type in a new ``ArrayTypeSignature`` with ``dimensions`` set as dimensions | +| ``MakeArrayType(ArrayDimension[] dimensions)`` | Wraps the type in a new ``ArrayTypeSignature`` with ``dimensions`` set as dimensions | +-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ | ``MakeByReferenceType()`` | Wraps the type in a new ``ByReferenceTypeSignature`` | +-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ @@ -219,3 +219,70 @@ Below an overview of all factory shortcut methods: +-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ | ``MakeGenericInstanceType(TypeSignature[] typeArguments)`` | Wraps the type in a new ``GenericInstanceTypeSignature`` with the provided type arguments. | +-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ + + + +Comparing Types +--------------- + +Type signatures can be tested for semantic equivalence using the ``SignatureComparer`` class. +Most use-cases of this class will not require any customization. +In these cases, the default ``SignatureComparer`` can be used: + +.. code-block:: csharp + + var comparer = SignatureComparer.Default; + + +However, if you wish to configure the comparer (e.g., for relaxing some of the declaring assembly version comparison rules), it is possible to create a new instance instead: + +.. code-block:: csharp + + var comparer = new SignatureComparer(SignatureComparisonFlags.AllowNewerVersions); + + +Once a comparer is obtained, we can test for type equality using any of the overloaded ``Equals`` methods: + +.. code-block:: csharp + + TypeSignature type1 = ...; + TypeSignature type2 = ...; + + if (comparer.Equals(type1, type2)) + { + // type1 and type2 are semantically equivalent. + } + + +The ``SignatureComparer`` class implements various instances of the ``IEqualityComparer`` interface, and as such, it can be used as a comparer for dictionaries and related types: + +.. code-block:: csharp + + var dictionary = new Dictionary(comparer); + + +.. note:: + + The ``SignatureComparer`` class also implements equality comparers for other kinds of metadata, such as field and method descriptors and their signatures. + + +In some cases, however, exact type equivalence is too strict of a test. +Since .NET facilitates an object oriented environment, many types will inherit or derive from each other, making it difficult to pinpoint exactly which types we would need to compare to test whether two types are compatible with each other. + +Section I.8.7 of the ECMA-335 specification defines a set of rules that dictate whether values of a certain type are compatible with or assignable to variables of another type. +These rules are implemented in AsmResolver using the ``IsCompatibleWith`` and ``IsAssignableTo`` methods: + +.. code-block:: csharp + + if (type1.IsCompatibleWith(type2)) + { + // type1 can be converted to type2. + } + + +.. code-block:: csharp + + if (type1.IsAssignableTo(type2)) + { + // Values of type1 can be assigned to variables of type2. + } diff --git a/docs/index.rst b/docs/index.rst index 74e1d1ad9..db82c1e9f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -72,3 +72,14 @@ Table of Contents: dotnet/type-memory-layout dotnet/bundles dotnet/advanced-pe-image-building.rst + + + +.. toctree:: + :maxdepth: 1 + :caption: PDB Symbols + :name: sec-pdb + + pdb/index + pdb/basics + pdb/symbols diff --git a/docs/pdb/basics.rst b/docs/pdb/basics.rst new file mode 100644 index 000000000..a2ad77c05 --- /dev/null +++ b/docs/pdb/basics.rst @@ -0,0 +1,113 @@ +Basic I/O +========= + +Every PDB image interaction is done through classes defined by the ``AsmResolver.Symbols.Pdb`` namespace: + +.. code-block:: csharp + + using AsmResolver.Symbols.Pdb; + + +Creating a new PDB Image +------------------------ + +Creating a new image can be done by instantiating a ``PdbImage`` class: + +.. code-block:: csharp + + var image = new PdbImage(); + + +Opening a PDB Image +------------------- + +Opening a PDB Image can be done through one of the ``FromXXX`` methods from the ``PdbImage`` class: + +.. code-block:: csharp + + byte[] raw = ... + var image = PdbImage.FromBytes(raw); + +.. code-block:: csharp + + var image = PdbImage.FromFile(@"C:\myfile.pdb"); + +.. code-block:: csharp + + MsfFile msfFile = ... + var image = PdbImage.FromFile(msfFile); + +.. code-block:: csharp + + BinaryStreamReader reader = ... + var image = PdbImage.FromReader(reader); + + +If you want to read large files (+100MB), consider using memory mapped I/O instead: + +.. code-block:: csharp + + using var service = new MemoryMappedFileService(); + var image = PdbImage.FromFile(service.OpenFile(@"C:\myfile.pdb")); + + +Writing a PDB Image +------------------- + +Writing PDB images directly is currently not supported yet, however there are plans to making this format fully serializable. + + +Creating a new MSF File +----------------------- + +Multi-Stream Format (MSF) files are files that form the backbone structure of all PDB images. +AsmResolver fully supports this lower level type of access to MSF files using the ``MsfFile`` class. + +To create a new MSF file, use one of its constructors: + +.. code-block:: csharp + + var msfFile = new MsfFile(); + + +.. code-block:: csharp + + var msfFile = new MsfFile(blockSize: 4096); + + +Opening an MSF File +------------------- + +Opening existing MSF files can be done in a very similar fashion as reading a PDB Image: + +.. code-block:: csharp + + byte[] raw = ... + var msfFile = MsfFile.FromBytes(raw); + +.. code-block:: csharp + + var msfFile = MsfFile.FromFile(@"C:\myfile.pdb"); + +.. code-block:: csharp + + BinaryStreamReader reader = ... + var msfFile = MsfFile.FromReader(reader); + + +Similar to reading PDB images, if you want to read large files (+100MB), consider using memory mapped I/O instead: + +.. code-block:: csharp + + using var service = new MemoryMappedFileService(); + var msfFile = MsfFile.FromFile(service.OpenFile(@"C:\myfile.pdb")); + + +Writing an MSF File +------------------- + +Writing an MSF file can be done through one of the ``Write`` method overloads. + +.. code-block:: csharp + + msfFile.Write(@"C:\myfile.patched.pdb"); diff --git a/docs/pdb/index.rst b/docs/pdb/index.rst new file mode 100644 index 000000000..f6adf7c65 --- /dev/null +++ b/docs/pdb/index.rst @@ -0,0 +1,14 @@ +Overview +======== + +The Program Database (PDB) file format is a format developed by Microsoft for storing debugging information about binary files. +PDBs are typically constructed based on the original source code the binary was compiled with, and lists various symbols that the source code defines and/or references. + +Since version 5.0, AsmResolver provides a work-in-progress implementation for reading (and sometimes writing) PDB files to allow for better analysis of compiled binaries. +This implementation is fully managed, and thus does not depend on libraries such as the Debug Interface Access (DIA) that only work on the Windows platform. +Furthermore, this project also aims to provide additional documentation on the file format, to make it more accessible to other developers. + +.. warning:: + + As the PDB file format is not very well documented, and mostly is reverse engineered from the official implementation provided by Microsoft, not everything in this API is finalized or stable yet. + As such, this part of AsmResolver's API is still likely to undergo some breaking changes as development continues. \ No newline at end of file diff --git a/docs/pdb/symbols.rst b/docs/pdb/symbols.rst new file mode 100644 index 000000000..584b6f0d1 --- /dev/null +++ b/docs/pdb/symbols.rst @@ -0,0 +1,108 @@ +Symbols +======= + +Symbol records define the top-level metadata of a binary, such as public functions, global fields, compiler information, and user-defined type definitions. +They are stored in either the global symbols stream, or in individual module streams. + +In the following, we will discuss various methods for accessing symbols defined in a PDB file. + + +Global Symbols +-------------- + +The ``PdbImage`` class defines a ``Symbols`` property, exposing all globally defined symbols in the PDB: + +.. code-block:: csharp + + PdbImage image = ...; + + foreach (var symbol in image.Symbols) + { + Console.WriteLine(symbol); + } + + +You can use LINQ's collection extensions to obtain symbols of a specific type. +For example, the following iterates over all global public symbols: + +.. code-block:: csharp + + PdbImage image = ...; + + foreach (var symbol in image.Symbols.Where(s => s.CodeViewSymbolType == CodeViewSymbolType.Pub32)) + { + Console.WriteLine(symbol); + } + + +Most types of symbol are represented by their own dedicated subclass of ``CodeViewSymbol`` in AsmResolver. +To get a more strongly typed filtering, use the ``OfType()`` extension to also automatically cast to the specific symbol type. +The following iterates over all global public symbols, and automatically casts them to the appropriate ``PublicSymbol`` type: + +.. code-block:: csharp + + PdbImage image = ...; + + foreach (var symbol in image.Symbols.OfType()) + { + Console.WriteLine("Name: {0}, Section: {1}, Offset: {2:X8}.", + symbol.Name, + symbol.SegmentIndex, + symbol.Offset); + } + + +.. note:: + + Since this part of the API is a work-in-process, any symbol that is currently not supported or recognized by AsmResolver's parser will be represented by an instance of the ``UnknownSymbol`` class, exposing the raw data of the symbol. + + +Module Symbols +-------------- + +PDB images may define symbols that are only valid within the scope of a single module (typically single compilands and object files). +These can be accessed via the ``PdbImage::Modules`` property, and each module defines its own ``Symbols`` property: + + +.. code-block:: csharp + + PdbImage image = ...; + + foreach (var module in image.Modules) + { + Console.WriteLine(module.Name); + foreach (var symbol in image.Symbols) + Console.WriteLine("\t- {0}", symbol); + Console.WriteLine(); + } + + +Local Symbols +------------- + +Records such as the ``ProcedureSymbol`` class may contain multiple sub-symbols. +These type of symbols all implement ``ICodeViewScopeSymbol``, and define their own set of ``Symbols``. +This way, the symbol tree can be traversed recursively. + +Below is an example snippet of obtaining all local variable symbols defined within the ``DllMain`` function of a library: + +.. code-block:: csharp + + PdbImage image = ...; + + var module = image.Modules.First(m => m.Name == @"c:\simpledll\release\dllmain.obj"); + var procedure = module.Symbols.OfType().First(p => p.Name == "DllMain"); + + foreach (var local in image.Symbols.OfType()) + { + Console.WriteLine("Name: {0}, Type: {1}", + local.Name, + local.VariableType); + } + + +.. note:: + + In the PDB file format, symbols that define a scope (such as ``S_LPROC32`` records) end their scope with a special ``S_END`` symbol record in the file. + However, AsmResolver does **not** include these ending records in the list of symbols. + Ending records are automatically interpreted and inserted when appropriate during the writing process by AsmResolver, and should thus not be expected in the list, nor added to the list manually. \ No newline at end of file diff --git a/docs/pefile/headers.rst b/docs/pefile/headers.rst index fc011d58d..f320af19e 100644 --- a/docs/pefile/headers.rst +++ b/docs/pefile/headers.rst @@ -1,4 +1,4 @@ -PE headers +PE Headers ========== After you obtained an instance of the ``PEFile`` class, it is possible to read and edit various properties in the DOS header, COFF file header and optional header. They each have a designated property: diff --git a/docs/pefile/sections.rst b/docs/pefile/sections.rst index 605425cb6..2ff88951c 100644 --- a/docs/pefile/sections.rst +++ b/docs/pefile/sections.rst @@ -1,6 +1,6 @@ .. _pe-file-sections: -PE sections +PE Sections =========== Sections can be read and modified by accessing the ``PEFile.Sections`` property, which is a collection of ``PESection`` objects. diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs index 60fe3f5af..8264a7a2f 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs @@ -152,10 +152,10 @@ public DotNetDirectoryBuildResult CreateDirectory() return new DotNetDirectoryBuildResult(directory, _tokenMapping); } - private uint GetEntryPoint() + private DotNetEntryPoint GetEntryPoint() { if (Module.ManagedEntryPoint is null) - return 0; + return MetadataToken.Zero; var entryPointToken = MetadataToken.Zero; @@ -174,7 +174,7 @@ private uint GetEntryPoint() break; } - return entryPointToken.ToUInt32(); + return entryPointToken; } private void AddMethodSemantics(MetadataToken ownerToken, IHasSemantics provider) diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs index 41218981a..be0f359bd 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs @@ -94,11 +94,15 @@ public virtual DotNetDirectoryBuildResult CreateDotNetDirectory( // All types defs and refs are added to the buffer at this point. We can therefore safely start adding // TypeSpecs if they need to be preserved: - ImportTypeSpecsAndMemberRefsIfSpecified(module, buffer); + ImportTypeSpecsIfSpecified(module, buffer); - // Define all members in the added types. - buffer.DefineFields(discoveryResult.Fields); + // We need to define method definitions before member references, since member references can have method + // definitions in their parent. buffer.DefineMethods(discoveryResult.Methods); + ImportMemberRefsIfSpecified(module, buffer); + + // Define all remaining members in the added types. + buffer.DefineFields(discoveryResult.Fields); buffer.DefineProperties(discoveryResult.Properties); buffer.DefineEvents(discoveryResult.Events); buffer.DefineParameters(discoveryResult.Parameters); @@ -240,7 +244,7 @@ private void ImportBasicTablesIfSpecified(ModuleDefinition module, DotNetDirecto } } - private void ImportTypeSpecsAndMemberRefsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) + private void ImportTypeSpecsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) { if (module.DotNetDirectory is null) return; @@ -250,6 +254,12 @@ private void ImportTypeSpecsAndMemberRefsIfSpecified(ModuleDefinition module, Do ImportTables(module, TableIndex.TypeSpec, s => buffer.AddTypeSpecification(s, true)); } + } + + private void ImportMemberRefsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) + { + if (module.DotNetDirectory is null) + return; if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveMemberReferenceIndices) != 0) { diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index c3a6025cf..58e82defb 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -9,6 +9,7 @@ using AsmResolver.IO; using AsmResolver.PE.File; using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Win32Resources; using AsmResolver.PE.Win32Resources.Builder; namespace AsmResolver.DotNet.Bundles @@ -28,9 +29,6 @@ public class BundleManifest 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae }; - private static readonly byte[] AppBinaryPathPlaceholder = - Encoding.UTF8.GetBytes("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"); - private IList? _files; /// @@ -194,7 +192,7 @@ public static BundleManifest FromDataSource(IDataSource source, ulong offset) /// The read manifest. public static BundleManifest FromReader(BinaryStreamReader reader) => new SerializedBundleManifest(reader); - private static long FindInFile(IDataSource source, byte[] data) + internal static long FindInFile(IDataSource source, byte[] needle) { // Note: For performance reasons, we read data from the data source in blocks, such that we avoid // virtual-dispatch calls and do the searching directly on a byte array instead. @@ -206,12 +204,12 @@ private static long FindInFile(IDataSource source, byte[] data) { int read = source.ReadBytes(start, buffer, 0, buffer.Length); - for (int i = sizeof(ulong); i < read - data.Length; i++) + for (int i = sizeof(ulong); i < read - needle.Length; i++) { bool fullMatch = true; - for (int j = 0; fullMatch && j < data.Length; j++) + for (int j = 0; fullMatch && j < needle.Length; j++) { - if (buffer[i + j] != data[j]) + if (buffer[i + j] != needle[j]) fullMatch = false; } @@ -317,6 +315,7 @@ public void WriteUsingTemplate(Stream outputStream, in BundlerParameters paramet /// The parameters to use for bundling all files into a single executable. public void WriteUsingTemplate(IBinaryStreamWriter writer, BundlerParameters parameters) { + // Verify entry point assembly exists within the bundle and is a correct length. var appBinaryEntry = Files.FirstOrDefault(f => f.RelativePath == parameters.ApplicationBinaryPath); if (appBinaryEntry is null) throw new ArgumentException($"Application {parameters.ApplicationBinaryPath} does not exist within the bundle."); @@ -325,6 +324,7 @@ public void WriteUsingTemplate(IBinaryStreamWriter writer, BundlerParameters par if (appBinaryPathBytes.Length > 1024) throw new ArgumentException("Application binary path cannot exceed 1024 bytes."); + // Patch headers when necessary. if (!parameters.IsArm64Linux) EnsureAppHostPEHeadersAreUpToDate(ref parameters); @@ -333,21 +333,26 @@ public void WriteUsingTemplate(IBinaryStreamWriter writer, BundlerParameters par if (signatureAddress == -1) throw new ArgumentException("AppHost template does not contain the bundle signature."); - long appBinaryPathAddress = FindInFile(appHostTemplateSource, AppBinaryPathPlaceholder); + long appBinaryPathAddress = FindInFile(appHostTemplateSource, parameters.PathPlaceholder); if (appBinaryPathAddress == -1) throw new ArgumentException("AppHost template does not contain the application binary path placeholder."); + // Write template. writer.WriteBytes(parameters.ApplicationHostTemplate); + + // Append manifest. writer.Offset = writer.Length; ulong headerAddress = WriteManifest(writer, parameters.IsArm64Linux); + // Update header address in apphost template. writer.Offset = (ulong) signatureAddress - sizeof(ulong); writer.WriteUInt64(headerAddress); + // Replace binary path placeholder with actual path. writer.Offset = (ulong) appBinaryPathAddress; writer.WriteBytes(appBinaryPathBytes); - if (AppBinaryPathPlaceholder.Length > appBinaryPathBytes.Length) - writer.WriteZeroes(AppBinaryPathPlaceholder.Length - appBinaryPathBytes.Length); + if (parameters.PathPlaceholder.Length > appBinaryPathBytes.Length) + writer.WriteZeroes(parameters.PathPlaceholder.Length - appBinaryPathBytes.Length); } private static void EnsureAppHostPEHeadersAreUpToDate(ref BundlerParameters parameters) diff --git a/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs b/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs index 2e83c3c16..2dee2e335 100644 --- a/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs +++ b/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs @@ -1,6 +1,9 @@ +using System; using System.IO; +using System.Text; using AsmResolver.IO; using AsmResolver.PE; +using AsmResolver.PE.File; using AsmResolver.PE.File.Headers; using AsmResolver.PE.Win32Resources; @@ -11,6 +14,8 @@ namespace AsmResolver.DotNet.Bundles /// public struct BundlerParameters { + private const string DefaultPathPlaceholder = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"; + /// /// Initializes new bundler parameters. /// @@ -22,6 +27,7 @@ public struct BundlerParameters /// /// The name of the file in the bundle that contains the entry point of the application. /// + [Obsolete("Use BundlerParameters::FromTemplate instead.")] public BundlerParameters(string appHostTemplatePath, string appBinaryPath) : this(File.ReadAllBytes(appHostTemplatePath), appBinaryPath) { @@ -34,6 +40,7 @@ public BundlerParameters(string appHostTemplatePath, string appBinaryPath) /// /// The name of the file in the bundle that contains the entry point of the application. /// + [Obsolete("Use BundlerParameters::FromTemplate instead.")] public BundlerParameters(byte[] appHostTemplate, string appBinaryPath) { ApplicationHostTemplate = appHostTemplate; @@ -41,6 +48,7 @@ public BundlerParameters(byte[] appHostTemplate, string appBinaryPath) IsArm64Linux = false; Resources = null; SubSystem = SubSystem.WindowsCui; + PathPlaceholder = Encoding.UTF8.GetBytes(DefaultPathPlaceholder); } /// @@ -58,6 +66,7 @@ public BundlerParameters(byte[] appHostTemplate, string appBinaryPath) /// The path to copy the PE headers and Win32 resources from. This is typically the original native executable /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. /// + [Obsolete("Use BundlerParameters::FromTemplate instead.")] public BundlerParameters(string appHostTemplatePath, string appBinaryPath, string? imagePathToCopyHeadersFrom) : this( File.ReadAllBytes(appHostTemplatePath), @@ -80,6 +89,7 @@ public BundlerParameters(string appHostTemplatePath, string appBinaryPath, strin /// The binary to copy the PE headers and Win32 resources from. This is typically the original native executable /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. /// + [Obsolete("Use BundlerParameters::FromTemplate instead.")] public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, byte[]? imageToCopyHeadersFrom) : this( appHostTemplate, @@ -102,6 +112,7 @@ imageToCopyHeadersFrom is not null /// The binary to copy the PE headers and Win32 resources from. This is typically the original native executable /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. /// + [Obsolete("Use BundlerParameters::FromTemplate instead.")] public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, IDataSource? imageToCopyHeadersFrom) : this( appHostTemplate, @@ -124,6 +135,7 @@ imageToCopyHeadersFrom is not null /// The PE image to copy the headers and Win32 resources from. This is typically the original native executable /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. /// + [Obsolete("Use BundlerParameters::FromTemplate instead.")] public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, IPEImage? imageToCopyHeadersFrom) : this( appHostTemplate, @@ -143,6 +155,7 @@ public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, IPEImage? /// /// The subsystem to use in the final Windows PE binary. /// The resources to copy into the final Windows PE binary. + [Obsolete("Use BundlerParameters::FromTemplate instead.")] public BundlerParameters( byte[] appHostTemplate, string appBinaryPath, @@ -154,6 +167,7 @@ public BundlerParameters( IsArm64Linux = false; SubSystem = subSystem; Resources = resources; + PathPlaceholder = Encoding.UTF8.GetBytes(DefaultPathPlaceholder); } /// @@ -183,6 +197,16 @@ public string ApplicationBinaryPath set; } + /// + /// Gets or sets the path placeholder in that will be replaced with the + /// contents of . + /// + public byte[] PathPlaceholder + { + get; + set; + } + /// /// Gets a value indicating whether the bundled executable targets the Linux operating system on ARM64. /// @@ -217,5 +241,182 @@ public SubSystem SubSystem get; set; } + + /// + /// Initializes new bundler parameters from an apphost template. + /// + /// + /// The path to the application host file template to use. By default this is stored in + /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or + /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native. + /// + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + public static BundlerParameters FromTemplate(string appHostTemplatePath, string appBinaryPath) + { + return FromTemplate(appHostTemplatePath, appBinaryPath, null); + } + + /// + /// Initializes new bundler parameters from an apphost template. + /// + /// + /// The path to the application host file template to use. By default this is stored in + /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or + /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native. + /// + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// The path to the binary to copy the PE headers and Win32 resources from. This is typically the original + /// native executable file that hosts the CLR, or the original AppHost file the bundle was extracted from. + /// + public static BundlerParameters FromTemplate(string appHostTemplatePath, string appBinaryPath, string? imagePathToCopyHeadersFrom) + { + return FromTemplate( + File.ReadAllBytes(appHostTemplatePath), + appBinaryPath, + imagePathToCopyHeadersFrom is not null + ? File.ReadAllBytes(imagePathToCopyHeadersFrom) + : null); + } + + /// + /// Initializes new bundler parameters from an apphost template. + /// + /// The application host template file to use. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + public static BundlerParameters FromTemplate(byte[] appHostTemplate, string appBinaryPath) + { + return FromTemplate(appHostTemplate, appBinaryPath, default(PEImage?)); + } + + /// + /// Initializes new bundler parameters from an apphost template. + /// + /// The application host template file to use. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// The binary to copy the PE headers and Win32 resources from. This is typically the original native executable + /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. + /// + public static BundlerParameters FromTemplate(byte[] appHostTemplate, string appBinaryPath, byte[]? imageToCopyHeadersFrom) + { + var image = imageToCopyHeadersFrom is not null + ? PEImage.FromBytes(imageToCopyHeadersFrom) + : null; + + return FromTemplate(appHostTemplate, appBinaryPath, image); + } + + /// + /// Initializes new bundler parameters from an apphost template. + /// + /// The application host template file to use. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// The image to copy the PE headers and Win32 resources from. This is typically the original native executable + /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. + /// + public static BundlerParameters FromTemplate(byte[] appHostTemplate, string appBinaryPath, IPEImage? imageToCopyHeadersFrom) + { + return new BundlerParameters + { + ApplicationHostTemplate = appHostTemplate, + ApplicationBinaryPath = appBinaryPath, + PathPlaceholder = Encoding.UTF8.GetBytes(DefaultPathPlaceholder), + IsArm64Linux = false, + Resources = imageToCopyHeadersFrom?.Resources, + SubSystem = imageToCopyHeadersFrom?.SubSystem ?? SubSystem.WindowsCui, + }; + } + + /// + /// Extracts bundler parameters from an existing packaged bundled PE file. + /// + /// The path to the original PE file. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// This method uses heuristics to determine the right offsets within the existing apphost bundle file, and is + /// not guaranteed to always produce the right bundler parameters. + /// + public static BundlerParameters FromExistingBundle(string originalFile, string appBinaryPath) + { + return FromExistingBundle(File.ReadAllBytes(originalFile), appBinaryPath, appBinaryPath); + } + + /// + /// Extracts bundler parameters from an existing packaged bundled PE file. + /// + /// The raw contents of the original PE file. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// This method uses heuristics to determine the right offsets within the existing apphost bundle file, and is + /// not guaranteed to always produce the right bundler parameters. + /// + public static BundlerParameters FromExistingBundle(byte[] originalFile, string appBinaryPath) + { + return FromExistingBundle(originalFile, appBinaryPath, appBinaryPath); + } + + /// + /// Extracts bundler parameters from an existing packaged bundled PE file. + /// + /// The raw contents of the original PE file. + /// + /// The original name of the file in the bundle that contains the entry point of the application. + /// + /// + /// The new name of the file in the bundle that contains the entry point of the application. + /// + /// + /// This method uses heuristics to determine the right offsets within the existing apphost bundle file, and is + /// not guaranteed to always produce the right bundler parameters. + /// + public static BundlerParameters FromExistingBundle(byte[] originalFile, string originalAppBinaryPath, string newAppBinaryPath) + { + PEFile file; + try + { + file = PEFile.FromBytes(originalFile); + } + catch (Exception ex) + { + throw new NotSupportedException("Only valid PE files are currently supported for repackaging.", ex); + } + + // Strip original bundle and reserialize PE file to use as template. + file.EofData = null; + using var stream = new MemoryStream(); + file.Write(stream); + + // Construct a template path to search for in the PE. + byte[] pathPlaceholder = new byte[32]; + Encoding.UTF8.GetBytes( + originalAppBinaryPath, 0, originalAppBinaryPath.Length, + pathPlaceholder, 0); + + return new BundlerParameters + { + ApplicationHostTemplate = stream.ToArray(), + ApplicationBinaryPath = newAppBinaryPath, + PathPlaceholder = pathPlaceholder, + IsArm64Linux = false, + Resources = PEImage.FromFile(file).Resources, + SubSystem = file.OptionalHeader.SubSystem, + }; + } } } diff --git a/src/AsmResolver.DotNet/Collections/Parameter.cs b/src/AsmResolver.DotNet/Collections/Parameter.cs index feca2527a..ce23969a0 100644 --- a/src/AsmResolver.DotNet/Collections/Parameter.cs +++ b/src/AsmResolver.DotNet/Collections/Parameter.cs @@ -76,6 +76,17 @@ public TypeSignature ParameterType /// public string Name => Definition?.Name ?? GetDummyArgumentName(MethodSignatureIndex); + /// + /// Creates a or returns the existing corresponding to this parameter. + /// If a is created it is automatically added to the method definition. + /// + public ParameterDefinition GetOrCreateDefinition() + { + if (_parentCollection is null) + throw new InvalidOperationException("Cannot create a parameter definition for a parameter that has been removed from its parent collection."); + return _parentCollection.GetOrCreateParameterDefinition(this); + } + [SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] private static string GetDummyArgumentName(int index) { diff --git a/src/AsmResolver.DotNet/Collections/ParameterCollection.cs b/src/AsmResolver.DotNet/Collections/ParameterCollection.cs index a2e8b4c5d..1692a8db3 100644 --- a/src/AsmResolver.DotNet/Collections/ParameterCollection.cs +++ b/src/AsmResolver.DotNet/Collections/ParameterCollection.cs @@ -127,11 +127,24 @@ private void UpdateParameterTypes() private TypeSignature? GetThisParameterType() { - if (_owner.DeclaringType is null) + var declaringType = _owner.DeclaringType; + if (declaringType is null) return null; - var result = _owner.DeclaringType.ToTypeSignature(); - if (_owner.DeclaringType.IsValueType) + TypeSignature result; + if (declaringType.GenericParameters.Count > 0) + { + var genArgs = new TypeSignature[declaringType.GenericParameters.Count]; + for (int i = 0; i < genArgs.Length; i++) + genArgs[i] = new GenericParameterSignature(_owner.Module, GenericParameterType.Type, i); + result = declaringType.MakeGenericInstanceType(genArgs); + } + else + { + result = declaringType.ToTypeSignature(); + } + + if (declaringType.IsValueType) result = result.MakeByReferenceType(); return result; @@ -142,6 +155,18 @@ private void UpdateParameterTypes() return _owner.ParameterDefinitions.FirstOrDefault(p => p.Sequence == sequence); } + internal ParameterDefinition GetOrCreateParameterDefinition(Parameter parameter) + { + if (parameter == ThisParameter) + throw new InvalidOperationException("Cannot retrieve a parameter definition for the virtual this parameter."); + if (parameter.Definition is not null) + return parameter.Definition; + + var parameterDefinition = new ParameterDefinition(parameter.Sequence, Utf8String.Empty, 0); + _owner.ParameterDefinitions.Add(parameterDefinition); + return parameterDefinition; + } + internal void PushParameterUpdateToSignature(Parameter parameter) { if (_owner.Signature is null) diff --git a/src/AsmResolver.DotNet/MemberNameGenerator.cs b/src/AsmResolver.DotNet/MemberNameGenerator.cs index 79170b52a..1c3f1344f 100644 --- a/src/AsmResolver.DotNet/MemberNameGenerator.cs +++ b/src/AsmResolver.DotNet/MemberNameGenerator.cs @@ -207,7 +207,7 @@ private static StringBuilder AppendSignatureParameterTypes(StringBuilder state, state.Append(", "); } - if (signature.IsSentinel) + if (signature.CallingConvention == CallingConventionAttributes.VarArg) state.Append("..."); return state; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index c766f2427..488090231 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -296,8 +296,13 @@ protected override IList GetExportedTypes() return null; } - if (DotNetDirectory.EntryPoint != 0) - return LookupMember(DotNetDirectory.EntryPoint) as IManagedEntryPoint; + if (DotNetDirectory.EntryPoint.IsManaged) + { + var token = DotNetDirectory.EntryPoint.MetadataToken; + return !TryLookupMember(token, out var member) + ? ReaderContext.BadImageAndReturn("Module contained an invalid managed entry point metadata token.") + : member; + } return null; } diff --git a/src/AsmResolver.DotNet/Signatures/CallingConventionAttributes.cs b/src/AsmResolver.DotNet/Signatures/CallingConventionAttributes.cs index 6768d236d..02594d23c 100644 --- a/src/AsmResolver.DotNet/Signatures/CallingConventionAttributes.cs +++ b/src/AsmResolver.DotNet/Signatures/CallingConventionAttributes.cs @@ -64,6 +64,12 @@ public enum CallingConventionAttributes : byte /// GenericInstance = 0xA, + /// + /// Indicates the method supports supplying a variable amount of arguments. This really is exactly native + /// varargs (no cookie) and should only appear in PInvoke IL stubs. + /// + NativeVarArg = 0xB, + /// /// Indicates the member defines generic parameters. /// @@ -83,6 +89,7 @@ public enum CallingConventionAttributes : byte /// /// Indicates the signature is part of a vararg method signature. /// + [Obsolete("This value should not be used. Use VarArg instead.")] Sentinel = 0x41, } } diff --git a/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs b/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs index e58903d30..1ea7e53ca 100644 --- a/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs @@ -25,7 +25,7 @@ public abstract class CallingConventionSignature : ExtendableBlobSignature, IImp bool readToEnd = true) { var signature = ReadSignature(ref context, ref reader); - if (readToEnd) + if (readToEnd && reader.CanRead(1)) { byte[] extraData = reader.ReadToEnd(); if (signature is not null) @@ -50,6 +50,7 @@ public abstract class CallingConventionSignature : ExtendableBlobSignature, IImp case CallingConventionAttributes.ThisCall: case CallingConventionAttributes.VarArg: case CallingConventionAttributes.Unmanaged: + case CallingConventionAttributes.NativeVarArg: return MethodSignature.FromReader(ref context, ref reader); case CallingConventionAttributes.Property: @@ -86,6 +87,15 @@ public CallingConventionAttributes Attributes set; } + /// + /// When this signature references a method signature, gets or sets the calling convention that is used. + /// + public CallingConventionAttributes CallingConvention + { + get => Attributes & SignatureTypeMask; + set => Attributes = (Attributes & ~SignatureTypeMask) | value; + } + /// /// Gets a value indicating whether the signature describes a method. /// @@ -139,16 +149,6 @@ public bool ExplicitThis | (value ? CallingConventionAttributes.ExplicitThis : 0); } - /// - /// Gets or sets a value indicating whether the signature is part of a vararg method signature. - /// - public bool IsSentinel - { - get => (Attributes & CallingConventionAttributes.Sentinel) != 0; - set => Attributes = (Attributes & ~CallingConventionAttributes.Sentinel) - | (value ? CallingConventionAttributes.Sentinel : 0); - } - /// public abstract bool IsImportedInModule(ModuleDefinition module); diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/MarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/MarshalDescriptor.cs index 4158db818..989a3213f 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/MarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/MarshalDescriptor.cs @@ -30,7 +30,9 @@ public static MarshalDescriptor FromReader(ModuleDefinition parentModule, ref Bi _ => new SimpleMarshalDescriptor(nativeType) }; - descriptor.ExtraData = reader.ReadToEnd(); + if (reader.CanRead(1)) + descriptor.ExtraData = reader.ReadToEnd(); + return descriptor; } diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs index 0651a80fb..d7c60e10d 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs @@ -226,11 +226,12 @@ public override string ToString() string parameterTypesString = string.Join(", ", ParameterTypes); string sentinelSuffix; - if (IsSentinel) + if (CallingConvention == CallingConventionAttributes.VarArg) { sentinelSuffix = ParameterTypes.Count > 0 ? ", ..." - : " ...";} + : "..."; + } else { sentinelSuffix = string.Empty; diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs b/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs index 4da04a96b..08c7b1703 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs @@ -120,17 +120,17 @@ protected void ReadParametersAndReturnType(ref BlobReaderContext context, ref Bi // Parameter types. _parameterTypes.Capacity = (int) parameterCount; - bool sentinel = false; + IncludeSentinel = false; for (int i = 0; i < parameterCount; i++) { var parameterType = TypeSignature.FromReader(ref context, ref reader); if (parameterType.ElementType == ElementType.Sentinel) { - sentinel = true; + IncludeSentinel = true; i--; } - else if (sentinel) + else if (IncludeSentinel) { SentinelParameterTypes.Add(parameterType); } @@ -146,7 +146,11 @@ protected void ReadParametersAndReturnType(ref BlobReaderContext context, ref Bi /// protected void WriteParametersAndReturnType(BlobSerializationContext context) { - context.Writer.WriteCompressedUInt32((uint) ParameterTypes.Count); + uint totalCount = (uint) ParameterTypes.Count; + if (IncludeSentinel) + totalCount += (uint) SentinelParameterTypes.Count; + + context.Writer.WriteCompressedUInt32(totalCount); ReturnType.Write(context); @@ -171,7 +175,7 @@ protected void WriteParametersAndReturnType(BlobSerializationContext context) public int GetTotalParameterCount() { int count = ParameterTypes.Count + SentinelParameterTypes.Count; - if (HasThis || ExplicitThis) + if (HasThis && !ExplicitThis) count++; return count; } diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs new file mode 100644 index 000000000..e073beb19 --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; + +namespace AsmResolver.DotNet.Signatures.Types +{ + /// + /// Represents a type signature representing an array. + /// + public abstract class ArrayBaseTypeSignature : TypeSpecificationSignature + { + /// + /// Initializes an array type signature. + /// + /// The element type of the array. + protected ArrayBaseTypeSignature(TypeSignature baseType) + : base(baseType) + { + } + + /// + public override bool IsValueType => false; + + /// + /// Gets the number of dimensions this array defines. + /// + public abstract int Rank + { + get; + } + + /// + /// Obtains the dimensions this array defines. + /// + /// The dimensions. + public abstract IEnumerable GetDimensions(); + + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) + { + if (base.IsDirectlyCompatibleWith(other, comparer)) + return true; + + TypeSignature? elementType = null; + if (other is ArrayBaseTypeSignature otherArrayType && Rank == otherArrayType.Rank) + { + // Arrays are only compatible if they have the same rank. + elementType = otherArrayType.BaseType; + } + else if (Rank == 1 + && other is GenericInstanceTypeSignature genericInstanceType + && genericInstanceType.GenericType.IsTypeOf("System.Collections.Generic", "IList`1")) + { + // Arrays are also compatible with IList if they've only one dimension. + elementType = genericInstanceType.TypeArguments[0]; + } + + if (elementType is null) + return false; + + var v = BaseType.GetUnderlyingType(); + var w = elementType.GetUnderlyingType(); + + return comparer.Equals(v.GetReducedType(), w.GetReducedType()) + || v.IsCompatibleWith(w, comparer); + } + } +} diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs index 682ec1f4e..056f943f6 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs @@ -13,7 +13,7 @@ namespace AsmResolver.DotNet.Signatures.Types /// /// For simple single-dimension arrays, use instead. /// - public class ArrayTypeSignature : TypeSpecificationSignature + public class ArrayTypeSignature : ArrayBaseTypeSignature { /// /// Creates a new array type signature. @@ -58,9 +58,6 @@ public ArrayTypeSignature(TypeSignature baseType, params ArrayDimension[] dimens /// public override string Name => $"{BaseType.Name ?? NullTypeToString}{GetDimensionsString()}"; - /// - public override bool IsValueType => false; - /// /// Gets a collection of dimensions. /// @@ -69,6 +66,12 @@ public IList Dimensions get; } + /// + public override int Rank => Dimensions.Count; + + /// + public override IEnumerable GetDimensions() => Dimensions; + internal new static ArrayTypeSignature FromReader(ref BlobReaderContext context, ref BinaryStreamReader reader) { var signature = new ArrayTypeSignature(TypeSignature.FromReader(ref context, ref reader)); @@ -191,6 +194,12 @@ public bool Validate() return true; } + /// + public override TypeSignature? GetDirectBaseClass() => Module?.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "Array") + .ToTypeSignature(false) + .ImportWith(Module.DefaultImporter); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitArrayType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/ByReferenceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ByReferenceTypeSignature.cs index ab0f38b9e..79f7cc202 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ByReferenceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ByReferenceTypeSignature.cs @@ -1,3 +1,4 @@ +using System; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -25,6 +26,24 @@ public ByReferenceTypeSignature(TypeSignature baseType) /// public override bool IsValueType => false; + /// + public override TypeSignature GetVerificationType() + { + if (Module is null) + throw new InvalidOperationException("Cannot determine verification type of a non-imported type."); + + var factory = Module.CorLibTypeFactory; + return BaseType.GetReducedType().ElementType switch + { + ElementType.I1 or ElementType.Boolean => factory.SByte.MakeByReferenceType(), + ElementType.I2 or ElementType.Char => factory.Int16.MakeByReferenceType(), + ElementType.I4 => factory.Int32.MakeByReferenceType(), + ElementType.I8 => factory.Int64.MakeByReferenceType(), + ElementType.I => factory.IntPtr.MakeByReferenceType(), + _ => base.GetVerificationType() + }; + } + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitByReferenceType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs index 9f77eaba4..4e37694c6 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs @@ -102,6 +102,49 @@ public override IResolutionScope? Scope /// public override ITypeDefOrRef ToTypeDefOrRef() => Type; + /// + public override TypeSignature GetReducedType() + { + var factory = Module!.CorLibTypeFactory; + return ElementType switch + { + ElementType.I1 or ElementType.U1 => factory.SByte, + ElementType.I2 or ElementType.U2 => factory.Int16, + ElementType.I4 or ElementType.U4 => factory.Int32, + ElementType.I8 or ElementType.U8 => factory.Int64, + ElementType.I or ElementType.U => factory.IntPtr, + _ => base.GetReducedType() + }; + } + + /// + public override TypeSignature GetVerificationType() + { + var factory = Module!.CorLibTypeFactory; + return GetReducedType().ElementType switch + { + ElementType.I1 or ElementType.Boolean => factory.SByte, + ElementType.I2 or ElementType.Char => factory.Int16, + ElementType.I4 => factory.Int32, + ElementType.I8 => factory.Int64, + ElementType.I => factory.IntPtr, + _ => base.GetVerificationType() + }; + } + + /// + public override TypeSignature GetIntermediateType() + { + var factory = Module!.CorLibTypeFactory; + var verificationType = GetVerificationType(); + return verificationType.ElementType switch + { + ElementType.I1 or ElementType.I2 or ElementType.I4 => factory.Int32, + ElementType.R4 or ElementType.R8 => factory.Double, // Technically, this is F. + _ => verificationType + }; + } + /// protected override void WriteContents(in BlobSerializationContext context) => context.Writer.WriteByte((byte) ElementType); diff --git a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs index 12b4554ad..051321466 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs @@ -66,6 +66,21 @@ public override string Name /// public override bool IsValueType => BaseType.IsValueType; + /// + public override TypeSignature GetReducedType() => BaseType.GetReducedType(); + + /// + public override TypeSignature GetVerificationType() => BaseType.GetVerificationType(); + + /// + public override TypeSignature GetIntermediateType() => BaseType.GetIntermediateType(); + + /// + public override TypeSignature? GetDirectBaseClass() => BaseType.GetDirectBaseClass(); + + /// + public override TypeSignature StripModifiers() => BaseType.StripModifiers(); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitCustomModifierType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs index b5ebe0c15..76beb10c8 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -52,6 +51,29 @@ public MethodSignature Signature /// public override bool IsImportedInModule(ModuleDefinition module) => Signature.IsImportedInModule(module); + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) + { + if (base.IsDirectlyCompatibleWith(other, comparer)) + return true; + + if (other is not FunctionPointerTypeSignature {Signature: { } otherSignature} + || Signature.GenericParameterCount != otherSignature.GenericParameterCount + || Signature.ParameterTypes.Count != otherSignature.ParameterTypes.Count + || !Signature.ReturnType.IsAssignableTo(otherSignature.ReturnType, comparer)) + { + return false; + } + + for (int i = 0; i < Signature.ParameterTypes.Count; i++) + { + if (!Signature.ParameterTypes[i].IsAssignableTo(otherSignature.ParameterTypes[i], comparer)) + return false; + } + + return true; + } + /// protected override void WriteContents(in BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 990652f5f..272ad4ebf 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -126,6 +127,81 @@ public override bool IsImportedInModule(ModuleDefinition module) return true; } + /// + public override TypeSignature? GetDirectBaseClass() + { + var genericType = GenericType.Resolve(); + if (genericType is null) + return null; + + // Interfaces have System.Object as direct base class. + if (genericType.IsInterface) + return Module!.CorLibTypeFactory.Object; + + if (genericType.BaseType is not { } baseType) + return null; + + // If the base type is not generic, treat it as a normal TypeDefOrRef. + if (baseType is TypeDefinition or TypeReference) + return baseType.ToTypeSignature(IsValueType); + + // At this point we expect a type specification. Substitute any generic type arguments present in it. + return baseType is TypeSpecification { Signature: { } signatureBaseType } + ? signatureBaseType.StripModifiers().InstantiateGenericTypes(GenericContext.FromType(this)) + : null; + } + + /// + public override IEnumerable GetDirectlyImplementedInterfaces() + { + var type = GenericType.Resolve(); + if (type is null) + return Enumerable.Empty(); + + var context = GenericContext.FromType(this); + return type.Interfaces.Select(i => i.Interface!.ToTypeSignature(false).InstantiateGenericTypes(context)); + } + + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) + { + if (base.IsDirectlyCompatibleWith(other, comparer)) + return true; + + // Other type must be a generic instance with the same generic base type and type argument count. + if (other is not GenericInstanceTypeSignature otherGenericInstance + || otherGenericInstance.TypeArguments.Count != TypeArguments.Count + || !comparer.Equals(GenericType, otherGenericInstance.GenericType)) + { + return false; + } + + // If resolution fails, assume no parameter variance. + var genericType = GenericType.Resolve(); + + // Check that every type argument is compatible with each other. + for (int i = 0; i < TypeArguments.Count; i++) + { + var variance = genericType?.GenericParameters[i].Attributes & GenericParameterAttributes.VarianceMask; + + bool argumentIsCompatible = variance switch + { + GenericParameterAttributes.NonVariant => + comparer.Equals(TypeArguments[i].StripModifiers(), otherGenericInstance.TypeArguments[i].StripModifiers()), + GenericParameterAttributes.Covariant => + TypeArguments[i].IsCompatibleWith(otherGenericInstance.TypeArguments[i], comparer), + GenericParameterAttributes.Contravariant => + otherGenericInstance.TypeArguments[i].IsCompatibleWith(TypeArguments[i], comparer), + _ => throw new ArgumentOutOfRangeException() + }; + + if (!argumentIsCompatible) + return false; + } + + return true; + } + /// protected override void WriteContents(in BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs index f29083c29..0c4b8f333 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs @@ -26,7 +26,7 @@ public GenericParameterSignature(GenericParameterType parameterType, int index) /// The module in which this generic parameter signature resides. /// Indicates the parameter signature is declared by a type or a method. /// The index of the referenced parameter. - public GenericParameterSignature(ModuleDefinition module, GenericParameterType parameterType, int index) + public GenericParameterSignature(ModuleDefinition? module, GenericParameterType parameterType, int index) { Scope = module; ParameterType = parameterType; diff --git a/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs index 8a3cb5f95..d558428ea 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs @@ -26,6 +26,21 @@ public PinnedTypeSignature(TypeSignature baseType) /// public override bool IsValueType => BaseType.IsValueType; + /// + public override TypeSignature GetReducedType() => BaseType.GetReducedType(); + + /// + public override TypeSignature GetVerificationType() => BaseType.GetVerificationType(); + + /// + public override TypeSignature GetIntermediateType() => BaseType.GetIntermediateType(); + + /// + public override TypeSignature? GetDirectBaseClass() => BaseType.GetDirectBaseClass(); + + /// + public override TypeSignature StripModifiers() => BaseType.StripModifiers(); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitPinnedType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs index 382c6cfb5..57424eb46 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs @@ -25,6 +25,20 @@ public PointerTypeSignature(TypeSignature baseType) /// public override bool IsValueType => false; + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) + { + if (base.IsDirectlyCompatibleWith(other, comparer)) + return true; + + if (other is not PointerTypeSignature otherPointer) + return false; + + var v = BaseType.GetVerificationType(); + var w = otherPointer.BaseType.GetVerificationType(); + return comparer.Equals(v, w); + } + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitPointerType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs index a952b5a0e..c0286ccf9 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -5,8 +6,10 @@ namespace AsmResolver.DotNet.Signatures.Types /// /// Represents a type signature describing a single dimension array with 0 as a lower bound. /// - public class SzArrayTypeSignature : TypeSpecificationSignature + public class SzArrayTypeSignature : ArrayBaseTypeSignature { + private static readonly ArrayDimension[] SzDimensions = { new() }; + /// /// Creates a new single-dimension array signature with 0 as a lower bound. /// @@ -23,7 +26,17 @@ public SzArrayTypeSignature(TypeSignature baseType) public override string Name => $"{BaseType.Name ?? NullTypeToString}[]"; /// - public override bool IsValueType => false; + public override int Rank => 1; + + /// + public override IEnumerable GetDimensions() => SzDimensions; + + /// + public override TypeSignature? GetDirectBaseClass() => Module?.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "Array") + .ToTypeSignature(false) + .ImportWith(Module.DefaultImporter); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index 3fed04061..319d76094 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -73,6 +75,49 @@ public ITypeDefOrRef Type /// public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => Type; + /// + public override TypeSignature GetUnderlyingType() + { + var type = Type.Resolve(); + + if (type is {IsEnum: true}) + return type.GetEnumUnderlyingType() ?? this; + + return this; + } + + /// + public override TypeSignature GetReducedType() + { + var underlyingType = GetUnderlyingType(); + return !ReferenceEquals(underlyingType, this) + ? underlyingType.GetReducedType() + : this; + } + + /// + public override TypeSignature? GetDirectBaseClass() + { + var type = Type.Resolve(); + if (type is null) + return null; + + // Interfaces have System.Object as direct base class. + return type.IsInterface + ? Module!.CorLibTypeFactory.Object + : type.BaseType!.ToTypeSignature(false).StripModifiers(); + } + + /// + public override IEnumerable GetDirectlyImplementedInterfaces() + { + var type = Type.Resolve(); + if (type is null) + return Enumerable.Empty(); + + return type.Interfaces.Select(i => i.Interface!.ToTypeSignature(false)); + } + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitTypeDefOrRef(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 6bd697c38..01e56d85f 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using AsmResolver.DotNet.Signatures.Types.Parsing; using AsmResolver.IO; @@ -310,8 +312,183 @@ internal static void WriteFieldOrPropType(BlobSerializationContext context, Type /// Gets the underlying base type signature, without any extra adornments. /// /// The base signature. + /// + /// This is not to be confused with , which may resolve enum types to their + /// underlying type representation. + /// public abstract ITypeDefOrRef? GetUnderlyingTypeDefOrRef(); + /// + /// Obtains the underlying type of the type signature. + /// + /// The underlying type. + /// + /// This method computes the underlying type as per ECMA-335 I.8.7, and may therefore attempt to resolve + /// assemblies to determine whether the type is an enum or not. It should not be confused with + /// , which merely obtains the instance + /// behind the type signature. + /// + public virtual TypeSignature GetUnderlyingType() => this; + + /// + /// Obtains the reduced type of the type signature. + /// + /// The reduced type. + /// + /// As per ECMA-335 I.8.7, the reduced type ignores the semantic differences between enumerations and the signed + /// and unsigned integer types; treating these types the same if they have the same number of bits. + /// + public virtual TypeSignature GetReducedType() => this; + + /// + /// Obtains the verification type of the type signature. + /// + /// The verification type. + /// + /// As per ECMA-335 I.8.7, the verification type ignores the semantic differences between enumerations, + /// characters, booleans, the signed and unsigned integer types, and managed pointers to any of these; treating + /// these types the same if they have the same number of bits or point to types with the same number of bits. + /// + public virtual TypeSignature GetVerificationType() => this; + + /// + /// Obtains the intermediate type of the type signature. + /// + /// The intermediate type. + /// + /// As per ECMA-335 I.8.7, intermediate types are a subset of the built-in value types can be represented on the + /// evaluation stack. + /// + public virtual TypeSignature GetIntermediateType() => GetVerificationType(); + + /// + /// Obtains the direct base class of the type signature. + /// + /// The type representing the immediate base class. + /// + /// The direct base class is computed according to the rules defined in ECMA-335 I.8.7, where interfaces + /// will extend , and generic base types will be instantiated with the derived + /// classes type arguments (if any). + /// + public virtual TypeSignature? GetDirectBaseClass() => null; + + /// + /// Obtains the interfaces that are directly implemented by the type. + /// + /// The interfaces. + /// + /// The result set of types is computed according to the rules defined in ECMA-335 I.8.7, where interfaces + /// will extend , and generic interfaces will be instantiated with the derived + /// classes type arguments (if any). + /// + public virtual IEnumerable GetDirectlyImplementedInterfaces() => Enumerable.Empty(); + + /// + /// Strips any top-level custom type modifier and pinned type annotations from the signature. + /// + /// The stripped type signature. + /// + /// This method does not necessarily recursively strip away every modifier type from the signature, nor does it + /// allocate new type signatures or change existing ones. It only traverses the type signature until a non-modifier + /// or pinned type is encountered. Annotations that are embedded in the type signature (e.g., as a type argument + /// of a generic instance type), will not be automatically removed. + /// + public virtual TypeSignature StripModifiers() => this; + + /// + /// Determines whether the current type is directly compatible with the provided type. + /// + /// The other type. + /// The comparer to use for comparing type signatures. + /// true if the types are directly compatible, false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.1., excluding the transitivity + /// rule. + /// + protected virtual bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) + { + return comparer.Equals(this, other); + } + + /// + /// Determines whether the current type is compatible with the provided type. + /// + /// The other type. + /// true if the type is compatible with , false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.1. + /// + public bool IsCompatibleWith(TypeSignature other) => IsCompatibleWith(other, SignatureComparer.Default); + + /// + /// Determines whether the current type is compatible with the provided type. + /// + /// The other type. + /// The comparer to use for comparing type signatures. + /// true if the type is compatible with , false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.1. + /// + public bool IsCompatibleWith(TypeSignature other, SignatureComparer comparer) + { + var current = StripModifiers(); + other = other.StripModifiers(); + + // Achieve the transitivity rule by moving up the type hierarchy iteratively. + while (current is not null) + { + // Is the current type compatible? + if (current.IsDirectlyCompatibleWith(other, comparer)) + return true; + + // Are any of the interfaces compatible instead? + foreach (var @interface in current.GetDirectlyImplementedInterfaces()) + { + if (@interface.IsCompatibleWith(other, comparer)) + return true; + } + + // If neither, move up type hierarchy. + current = current.GetDirectBaseClass()?.StripModifiers(); + } + + return false; + } + + /// + /// Determines whether the current type is assignable to the provided type. + /// + /// The other type. + /// true if the type is assignable to , false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.3. + /// + public bool IsAssignableTo(TypeSignature other) => IsAssignableTo(other, SignatureComparer.Default); + + /// + /// Determines whether the current type is assignable to the provided type. + /// + /// The other type. + /// The comparer to use for comparing type signatures. + /// true if the type is assignable to , false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.3. + /// + public bool IsAssignableTo(TypeSignature other, SignatureComparer comparer) + { + var intermediateType1 = GetIntermediateType(); + var intermediateType2 = other.GetIntermediateType(); + + if (comparer.Equals(intermediateType1, intermediateType2) + || intermediateType1.ElementType == ElementType.I && intermediateType2.ElementType == ElementType.I4 + || intermediateType1.ElementType == ElementType.I4 && intermediateType2.ElementType == ElementType.I) + { + return true; + } + + return IsCompatibleWith(other, comparer); + } + /// /// Substitutes any generic type parameter in the type signature with the parameters provided by /// the generic context. @@ -338,6 +515,7 @@ public TypeSignature InstantiateGenericTypes(GenericContext context) /// IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// /// Visit the current type signature using the provided visitor. /// diff --git a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs index a72535fa3..b9055445d 100644 --- a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs +++ b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs @@ -399,6 +399,16 @@ public IList DataDirectories /// The data directory entry. public DataDirectory GetDataDirectory(DataDirectoryIndex index) => DataDirectories[(int) index]; + /// + /// Sets a data directory by its index. + /// + /// The index. + /// The new data directory entry. + public void SetDataDirectory(DataDirectoryIndex index, DataDirectory directory) + { + DataDirectories[(int) index] = directory; + } + /// public override uint GetPhysicalSize() { diff --git a/src/AsmResolver.PE/DotNet/DotNetDirectory.cs b/src/AsmResolver.PE/DotNet/DotNetDirectory.cs index b6f7e21ca..6f4941cc7 100644 --- a/src/AsmResolver.PE/DotNet/DotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/DotNetDirectory.cs @@ -62,7 +62,7 @@ public DotNetDirectoryFlags Flags } /// - public uint EntryPoint + public DotNetEntryPoint EntryPoint { get; set; @@ -127,7 +127,7 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteUInt16(MinorRuntimeVersion); CreateDataDirectoryHeader(Metadata).Write(writer); writer.WriteUInt32((uint) Flags); - writer.WriteUInt32(EntryPoint); + writer.WriteUInt32(EntryPoint.GetRawValue()); CreateDataDirectoryHeader(DotNetResources).Write(writer); CreateDataDirectoryHeader(StrongName).Write(writer); CreateDataDirectoryHeader(CodeManagerTable).Write(writer); diff --git a/src/AsmResolver.PE/DotNet/DotNetEntryPoint.cs b/src/AsmResolver.PE/DotNet/DotNetEntryPoint.cs new file mode 100644 index 000000000..483b6bebf --- /dev/null +++ b/src/AsmResolver.PE/DotNet/DotNetEntryPoint.cs @@ -0,0 +1,118 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using AsmResolver.PE.DotNet.Metadata.Tables; + +namespace AsmResolver.PE.DotNet +{ + /// + /// Represents a reference to an entry point in the .NET Data Directory. This is either a metadata token to a + /// method or file defined in the .NET metadata tables, or a virtual address to native code located in a PE section. + /// + public readonly struct DotNetEntryPoint + { + /// + /// Constructs a new managed entry point reference. + /// + /// + /// The metadata token of the managed entry point. This must be either a method or a file token. + /// + public DotNetEntryPoint(MetadataToken token) + { + if (token != MetadataToken.Zero && token.Table is not TableIndex.Method or TableIndex.File) + throw new ArgumentException("Managed entry points can only be metadata tokens referencing a method or a file."); + + MetadataToken = token; + NativeCode = null; + } + + /// + /// Constructs a new native entry point reference. + /// + /// A reference to the native code implementing the entry point. + public DotNetEntryPoint(ISegmentReference nativeCode) + { + MetadataToken = MetadataToken.Zero; + NativeCode = nativeCode; + } + + /// + /// Gets a value indicating whether an entry point is present or not. + /// + public bool IsPresent => IsManaged || IsNative; + + /// + /// Gets a value indicating the entry point is present in the form of a metadata token. + /// + public bool IsManaged => MetadataToken != MetadataToken.Zero; + + /// + /// Gets a value indicating the entry point is present in the form of a reference to native code. + /// + [MemberNotNullWhen(true, nameof(NativeCode))] + public bool IsNative => NativeCode is not null; + + /// + /// When the entry point is managed, gets the metadata token of the method or file implementing the managed + /// entry point. + /// + public MetadataToken MetadataToken + { + get; + } + + /// + /// When the entry point is native, gets the reference to the beginning of the native code implementing the + /// native entry point. + /// + public ISegmentReference? NativeCode + { + get; + } + + /// + /// Gets the raw reference value as it would be written in the .NET data directory. + /// + public uint GetRawValue() => IsNative ? NativeCode.Rva : MetadataToken.ToUInt32(); + + /// + /// Constructs a new managed entry point reference. + /// + /// + /// The metadata token of the managed entry point. This must be either a method or a file token. + /// + public static implicit operator DotNetEntryPoint(MetadataToken token) => new(token); + + /// + /// Constructs a new native entry point reference. + /// + /// A reference to the native code implementing the entry point. + public static implicit operator DotNetEntryPoint(SegmentReference nativeCode) => new(nativeCode); + + /// + /// Constructs a new managed or native entry point reference. + /// + /// + /// The constructed entry point reference. + /// + /// This method is added for backwards source compatibility, and should only be used when absolutely necessary. + /// When the input value resembles a valid metadata token, that is, it is of the form 0x06XXXXXX or + /// 0x26XXXXXX, a managed entry point will be constructed. In any other case, the passed in integer will + /// be interpreted as a relative virtual address pointing to native code instead. + /// + [Obsolete("Construct entry points via a MetadataToken or an ISegmentReference instead.")] + public static implicit operator DotNetEntryPoint(uint value) + { + var token = new MetadataToken(value); + return token.Table is TableIndex.Method or TableIndex.File + ? new DotNetEntryPoint(token) + : new DotNetEntryPoint(new VirtualAddress(value)); + } + + /// + /// Gets the raw reference value of a .NET entry point reference as it would be written in the .NET data directory. + /// + /// The entry point reference. + /// The raw reference value. + public static implicit operator uint(DotNetEntryPoint value) => value.GetRawValue(); + } +} diff --git a/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs index 0e808191e..ba1524cdb 100644 --- a/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs @@ -55,7 +55,12 @@ DotNetDirectoryFlags Flags /// Gets or sets the metadata token or entry point virtual address, depending on whether /// is set in . /// - uint EntryPoint + /// + /// Setting this property will not alter . This means that even if a native entry point is + /// assigned to this property, the flag should be set + /// manually for a properly working .NET module. + /// + DotNetEntryPoint EntryPoint { get; set; diff --git a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs index 203c6e485..ffb0d51d6 100644 --- a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs @@ -1,6 +1,7 @@ using System; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Resources; using AsmResolver.PE.DotNet.VTableFixups; using AsmResolver.PE.File.Headers; @@ -39,7 +40,11 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader MinorRuntimeVersion = reader.ReadUInt16(); _metadataDirectory = DataDirectory.FromReader(ref reader); Flags = (DotNetDirectoryFlags) reader.ReadUInt32(); - EntryPoint = reader.ReadUInt32(); + + EntryPoint = (Flags & DotNetDirectoryFlags.NativeEntryPoint) != 0 + ? new DotNetEntryPoint(context.File.GetReferenceToRva(reader.ReadUInt32())) + : new DotNetEntryPoint(new MetadataToken(reader.ReadUInt32())); + _resourcesDirectory = DataDirectory.FromReader(ref reader); _strongNameDirectory = DataDirectory.FromReader(ref reader); _codeManagerDirectory = DataDirectory.FromReader(ref reader); diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj index 091cf3c05..b2802c3ce 100644 --- a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj +++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj @@ -12,12 +12,12 @@ true - bin\Debug\netstandard2.0\AsmResolver.Symbols.WindowsPdb.xml + bin\Debug\netstandard2.0\AsmResolver.Symbols.Pdb.xml true - bin\Release\netstandard2.0\AsmResolver.xml + bin\Release\netstandard2.0\AsmResolver.Symbols.Pdb.xml diff --git a/src/AsmResolver.Symbols.Pdb/ICodeViewSymbolProvider.cs b/src/AsmResolver.Symbols.Pdb/ICodeViewSymbolProvider.cs new file mode 100644 index 000000000..494bec3b7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/ICodeViewSymbolProvider.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using AsmResolver.Symbols.Pdb.Records; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Describes an object that defines a list of symbols. +/// +public interface ICodeViewSymbolProvider +{ + /// + /// Gets the list of defined symbols. + /// + public IList Symbols + { + get; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs index 32e5bccc7..ccd92bffe 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs @@ -6,7 +6,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a leaf containing a list of type arguments for a function or method. /// -public class ArgumentListLeaf : CodeViewLeaf +public class ArgumentListLeaf : CodeViewLeaf, ITpiLeaf { private IList? _types; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BuildInfoLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BuildInfoLeaf.cs new file mode 100644 index 000000000..ccf03b1ec --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BuildInfoLeaf.cs @@ -0,0 +1,19 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf containing build information for a file in a PDB image. +/// +public class BuildInfoLeaf : SubStringListLeaf +{ + /// + /// Initializes an empty build information leaf. + /// + /// The type index associated to the leaf. + protected BuildInfoLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BuildInfo; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs index 689105e37..c0cf1a75a 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -1,9 +1,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// -/// Represents a single record in a field list of a TPI or IPI stream. +/// Represents a single record in a field list of a TPI stream. /// -public abstract class CodeViewField : CodeViewLeaf +public abstract class CodeViewField : CodeViewLeaf, ITpiLeaf { /// /// Initializes an empty CodeView field leaf. diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs index c966aa91e..f9a567986 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -5,9 +5,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// -/// Represents a single leaf record in a TPI or IPI stream. +/// Represents the base for a single leaf record in a TPI or IPI stream. /// -public abstract class CodeViewLeaf +public abstract class CodeViewLeaf : ICodeViewLeaf { /// /// Initializes an empty CodeView leaf. @@ -18,24 +18,20 @@ 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) + internal static ICodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) { ushort length = reader.ReadUInt16(); var dataReader = reader.Fork(); @@ -55,11 +51,13 @@ internal static CodeViewLeaf FromReaderNoHeader( Array => new SerializedArrayTypeRecord(context, typeIndex, dataReader), ArgList => new SerializedArgumentListLeaf(context, typeIndex, dataReader), BClass => new SerializedBaseClassField(context, typeIndex, ref dataReader), + BuildInfo => new SerializedBuildInfoLeaf(context, typeIndex, dataReader), Class or Interface or Structure => new SerializedClassTypeRecord(kind, context, typeIndex, dataReader), BitField => new SerializedBitFieldTypeRecord(context, typeIndex, dataReader), Enum => new SerializedEnumTypeRecord(context, typeIndex, dataReader), Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader), + FuncId => new SerializedFunctionIdentifier(context, typeIndex, dataReader), Member => new SerializedInstanceDataField(context, typeIndex, ref dataReader), Method => new SerializedOverloadedMethod(context, typeIndex, ref dataReader), MethodList => new SerializedMethodListLeaf(context, typeIndex, dataReader), @@ -70,6 +68,8 @@ internal static CodeViewLeaf FromReaderNoHeader( Pointer => new SerializedPointerTypeRecord(context, typeIndex, dataReader), Procedure => new SerializedProcedureTypeRecord(context, typeIndex, dataReader), StMember => new SerializedStaticDataField(context, typeIndex, ref dataReader), + StringId => new SerializedStringIdentifier(context, typeIndex, dataReader), + SubstrList => new SerializedSubStringListLeaf(context, typeIndex, dataReader), Union => new SerializedUnionTypeRecord(context, typeIndex, dataReader), VFuncTab => new SerializedVTableField(context, typeIndex, ref dataReader), VTShape => new SerializedVTableShapeLeaf(context, typeIndex, dataReader), diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs index 6acb79d56..dcd0ffa3f 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs @@ -152,7 +152,7 @@ public enum CodeViewLeafKind : ushort FuncId = 0x1601, MFuncId = 0x1602, - Buildinfo = 0x1603, + BuildInfo = 0x1603, SubstrList = 0x1604, StringId = 0x1605, diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs index 097f66be0..30fe0a258 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a single type record in a TPI or IPI stream. /// -public abstract class CodeViewTypeRecord : CodeViewLeaf +public abstract class CodeViewTypeRecord : CodeViewLeaf, ITpiLeaf { /// /// Initializes an empty CodeView type record. diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs index 5eecc0abe..c30e842d4 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs @@ -6,7 +6,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a leaf containing a list of fields. /// -public class FieldListLeaf : CodeViewLeaf +public class FieldListLeaf : CodeViewLeaf, ITpiLeaf { private IList? _fields; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FunctionIdentifier.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FunctionIdentifier.cs new file mode 100644 index 000000000..326194b14 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/FunctionIdentifier.cs @@ -0,0 +1,86 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a function identifier, consisting of its name and its signature. +/// +public class FunctionIdentifier : CodeViewLeaf, IIpiLeaf +{ + private readonly LazyVariable _name; + private readonly LazyVariable _functionType; + + /// + /// Initializes an empty function identifier leaf. + /// + /// The type index. + protected FunctionIdentifier(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + _functionType = new LazyVariable(GetFunctionType); + } + + /// + /// Creates a new function identifier leaf. + /// + /// The identifier of the scope defining the function (if available). + /// The name of the function. + /// The type describing the shape of the function. + public FunctionIdentifier(uint scopeId, Utf8String name, CodeViewTypeRecord functionType) + : base(0) + { + ScopeId = scopeId; + _name = new LazyVariable(name); + _functionType = new LazyVariable(functionType); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FuncId; + + /// + /// Gets or sets the identifier of the scope defining the function (if available). + /// + public uint ScopeId + { + get; + set; + } + + /// + /// Gets or sets the name of the function. + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Gets or sets the type describing the shape of the function. + /// + public CodeViewTypeRecord? FunctionType + { + get => _functionType.Value; + set => _functionType.Value = value; + } + + /// + /// Obtains the name of the function. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + /// Obtains the type of the function. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetFunctionType() => null; + + /// + public override string ToString() => Name ?? "<<>>"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ICodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ICodeViewLeaf.cs new file mode 100644 index 000000000..43a12a4fa --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ICodeViewLeaf.cs @@ -0,0 +1,23 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single leaf record in a TPI or IPI stream. +/// +public interface ICodeViewLeaf +{ + /// + /// Gets the type kind this record encodes. + /// + CodeViewLeafKind LeafKind + { + get; + } + + /// + /// Gets the type index the type is associated to. + /// + uint TypeIndex + { + get; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/IIpiLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/IIpiLeaf.cs new file mode 100644 index 000000000..7d1323541 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/IIpiLeaf.cs @@ -0,0 +1,8 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single leaf record in an IPI stream. +/// +public interface IIpiLeaf : ICodeViewLeaf +{ +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ITpiLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ITpiLeaf.cs new file mode 100644 index 000000000..3c9e9ee32 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ITpiLeaf.cs @@ -0,0 +1,8 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single leaf record in a TPI stream. +/// +public interface ITpiLeaf : ICodeViewLeaf +{ +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs index 5069796fb..08fc5adcd 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a single instance member function. /// -public class MemberFunctionLeaf : CodeViewLeaf +public class MemberFunctionLeaf : CodeViewLeaf, ITpiLeaf { private readonly LazyVariable _returnType; private readonly LazyVariable _declaringType; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs index f9538bac3..17429adac 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs @@ -7,7 +7,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents a leaf record containing a list of overloaded methods. /// -public class MethodListLeaf : CodeViewLeaf +public class MethodListLeaf : CodeViewLeaf, ITpiLeaf { private IList? _entries; diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs index a8a54409c..e752748a7 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs @@ -35,7 +35,7 @@ protected override IList GetArgumentTypes() for (int i = 0; i < count; i++) { uint typeIndex = reader.ReadUInt32(); - if (!_context.ParentImage.TryGetLeafRecord(typeIndex, out var leaf) || leaf is not CodeViewTypeRecord t) + if (!_context.ParentImage.TryGetLeafRecord(typeIndex, out CodeViewTypeRecord? t)) { _context.Parameters.ErrorListener.BadImage( $"Argument list {TypeIndex:X8} contains an invalid argument type index {typeIndex:X8}."); diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs index 273df7536..801ec6293 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs @@ -35,7 +35,7 @@ public SerializedArrayTypeRecord(PdbReaderContext context, uint typeIndex, Binar /// protected override CodeViewTypeRecord? GetElementType() { - return _context.ParentImage.TryGetLeafRecord(_elementTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_elementTypeIndex, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Array type {TypeIndex:X8} contains an invalid element type index {_elementTypeIndex:X8}."); @@ -44,7 +44,7 @@ public SerializedArrayTypeRecord(PdbReaderContext context, uint typeIndex, Binar /// protected override CodeViewTypeRecord? GetIndexType() { - return _context.ParentImage.TryGetLeafRecord(_indexTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_indexTypeIndex, out 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 index 325e73010..18eb56b92 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs @@ -29,7 +29,7 @@ public SerializedBaseClassField(PdbReaderContext context, uint typeIndex, ref Bi /// protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out 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 index e18e87dfe..6e88e4407 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs @@ -28,7 +28,7 @@ public SerializedBitFieldTypeRecord(PdbReaderContext context, uint typeIndex, Bi /// protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out 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/SerializedBuildInfoLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBuildInfoLeaf.cs new file mode 100644 index 000000000..5b59759bb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBuildInfoLeaf.cs @@ -0,0 +1,34 @@ +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 SerializedBuildInfoLeaf : BuildInfoLeaf +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a build information leaf 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 SerializedBuildInfoLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + protected override IList GetEntries() + { + var reader = _reader.Fork(); + uint count = reader.ReadUInt16(); + return SerializedSubStringListLeaf.ReadEntries(_context, TypeIndex, count, ref reader); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs index 3773352ff..21e6595d6 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs @@ -1,3 +1,4 @@ +using System; using AsmResolver.IO; namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; @@ -32,7 +33,7 @@ public SerializedClassTypeRecord(CodeViewLeafKind kind, PdbReaderContext context _baseTypeIndex = reader.ReadUInt32(); _vTableShapeIndex = reader.ReadUInt32(); - Size = (uint) ReadNumeric(ref reader); + Size = Convert.ToUInt32(ReadNumeric(ref reader)); _nameReader = reader.Fork(); reader.AdvanceUntil(0, true); @@ -51,7 +52,7 @@ public SerializedClassTypeRecord(CodeViewLeafKind kind, PdbReaderContext context if (_baseTypeIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Class type {TypeIndex:X8} contains an invalid underlying enum type index {_baseTypeIndex:X8}."); @@ -63,7 +64,7 @@ public SerializedClassTypeRecord(CodeViewLeafKind kind, PdbReaderContext context if (_fieldIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out SerializedFieldListLeaf? list) ? list : _context.Parameters.ErrorListener.BadImageAndReturn( $"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); @@ -75,7 +76,7 @@ public SerializedClassTypeRecord(CodeViewLeafKind kind, PdbReaderContext context if (_vTableShapeIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_vTableShapeIndex, out var leaf) && leaf is VTableShapeLeaf shape + return _context.ParentImage.TryGetLeafRecord(_vTableShapeIndex, out 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 index e671bb176..42b48d2ea 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs @@ -36,7 +36,7 @@ public SerializedEnumTypeRecord(PdbReaderContext context, uint typeIndex, Binary /// protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_underlyingType, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}."); @@ -48,7 +48,7 @@ public SerializedEnumTypeRecord(PdbReaderContext context, uint typeIndex, Binary if (_fieldIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is FieldListLeaf list + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out 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/SerializedFunctionIdentifier.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFunctionIdentifier.cs new file mode 100644 index 000000000..878d8b667 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFunctionIdentifier.cs @@ -0,0 +1,42 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedFunctionIdentifier : FunctionIdentifier +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a function identifier from the provided input stream. + /// + /// The reading context in which the identifier is situated in. + /// The type index to assign to the identifier. + /// The input stream to read from. + public SerializedFunctionIdentifier(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + + ScopeId = reader.ReadUInt32(); + _typeIndex = reader.ReadUInt32(); + + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetFunctionType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Array type {TypeIndex:X8} contains an invalid element type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs index c5fcf252e..c641b79b0 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs @@ -41,7 +41,7 @@ public SerializedInstanceDataField(PdbReaderContext context, uint typeIndex, ref if (_dataTypeIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out 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 index 1b0cc2253..4f7a3bd1d 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs @@ -37,7 +37,7 @@ public SerializedMemberFunctionLeaf(PdbReaderContext context, uint typeIndex, Bi /// protected override CodeViewTypeRecord? GetReturnType() { - return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Member function {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); @@ -46,7 +46,7 @@ public SerializedMemberFunctionLeaf(PdbReaderContext context, uint typeIndex, Bi /// protected override CodeViewTypeRecord? GetDeclaringType() { - return _context.ParentImage.TryGetLeafRecord(_declaringTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_declaringTypeIndex, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Member function {TypeIndex:X8} contains an invalid declaring type index {_declaringTypeIndex:X8}."); @@ -55,7 +55,7 @@ public SerializedMemberFunctionLeaf(PdbReaderContext context, uint typeIndex, Bi /// protected override CodeViewTypeRecord? GetThisType() { - return _context.ParentImage.TryGetLeafRecord(_thisTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_thisTypeIndex, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Member function {TypeIndex:X8} contains an invalid this-type index {_thisTypeIndex:X8}."); @@ -67,7 +67,7 @@ public SerializedMemberFunctionLeaf(PdbReaderContext context, uint typeIndex, Bi if (_argumentListIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out 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 index af17da782..745c6ab1d 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs @@ -27,8 +27,8 @@ public SerializedMethodListEntry(PdbReaderContext context, ref BinaryStreamReade /// protected override MemberFunctionLeaf? GetFunction() { - return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type - ? type + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out MemberFunctionLeaf? function) + ? function : _context.Parameters.ErrorListener.BadImageAndReturn( $"Method list entry contains an invalid function type index {_functionIndex:X8}."); } diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs index a6c9b5bf3..b7decf9eb 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs @@ -27,7 +27,7 @@ public SerializedModifierTypeRecord(PdbReaderContext context, uint typeIndex, Bi /// protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out 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 index 9ebefafc0..9c319c2cc 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs @@ -33,7 +33,7 @@ public SerializedNestedTypeField(PdbReaderContext context, uint typeIndex, ref B /// protected override CodeViewTypeRecord? GetNestedType() { - return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out 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 index 08347ed9d..f8f5579da 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs @@ -35,8 +35,8 @@ public SerializedNonOverloadedMethod(PdbReaderContext context, uint typeIndex, r /// protected override MemberFunctionLeaf? GetFunction() { - return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type - ? type + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out MemberFunctionLeaf? function) + ? function : _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 index 3342850e3..eff7a027c 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs @@ -37,7 +37,7 @@ public SerializedOverloadedMethod(PdbReaderContext context, uint typeIndex, ref if (_methodListIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_methodListIndex, out var leaf) && leaf is MethodListLeaf list + return _context.ParentImage.TryGetLeafRecord(_methodListIndex, out 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 index 74a481134..a48f52a43 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs @@ -29,7 +29,7 @@ public SerializedPointerTypeRecord(PdbReaderContext context, uint typeIndex, Bin /// protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out 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 index 90465a8a9..7da35d9d6 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs @@ -32,7 +32,7 @@ public SerializedProcedureTypeRecord(PdbReaderContext context, uint typeIndex, B /// protected override CodeViewTypeRecord? GetReturnType() { - return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Procedure type {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); @@ -44,7 +44,7 @@ public SerializedProcedureTypeRecord(PdbReaderContext context, uint typeIndex, B if (_argumentListIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out 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 index 4196ac5c9..5b16ff071 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs @@ -37,7 +37,7 @@ public SerializedStaticDataField(PdbReaderContext context, uint typeIndex, ref B if (_dataTypeIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out 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/SerializedStringIdentifier.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStringIdentifier.cs new file mode 100644 index 000000000..8d8679798 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStringIdentifier.cs @@ -0,0 +1,42 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedStringIdentifier : StringIdentifier +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + private readonly uint _subStringsIndex; + + /// + /// Reads a string ID 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 SerializedStringIdentifier(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _subStringsIndex = reader.ReadUInt32(); + _reader = reader; + } + + /// + protected override Utf8String GetValue() => _reader.Fork().ReadUtf8String(); + + /// + protected override SubStringListLeaf? GetSubStrings() + { + if (_subStringsIndex == 0) + return null; + + return _context.ParentImage.TryGetIdLeafRecord(_subStringsIndex, out SubStringListLeaf? list) + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"String ID {TypeIndex:X8} contains an invalid substrings type index {_subStringsIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedSubStringListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedSubStringListLeaf.cs new file mode 100644 index 000000000..c599c9e5c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedSubStringListLeaf.cs @@ -0,0 +1,58 @@ +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 SerializedSubStringListLeaf : SubStringListLeaf +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a list of sub-strings 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 SerializedSubStringListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + protected override IList GetEntries() + { + var reader = _reader.Fork(); + uint count = reader.ReadUInt32(); + return ReadEntries(_context, TypeIndex, count, ref reader); + } + + internal static IList ReadEntries( + PdbReaderContext context, + uint originIndex, + uint count, + ref BinaryStreamReader reader) + { + var result = new List(); + + for (int i = 0; i < count; i++) + { + uint index = reader.ReadUInt32(); + if (!context.ParentImage.TryGetIdLeafRecord(index, out StringIdentifier? entry)) + { + context.Parameters.ErrorListener.BadImage( + $"String List {originIndex:X8} contains an invalid string index {index:X8}."); + return result; + } + result.Add(entry); + } + + return result; + } + +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs index 3a0169a39..a77273a32 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs @@ -47,7 +47,7 @@ public SerializedUnionTypeRecord(PdbReaderContext context, uint typeIndex, Binar if (_fieldIndex == 0) return null; - return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out 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 index 5be9b1165..f7e52e471 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs @@ -38,7 +38,7 @@ public SerializedVBaseClassField( /// protected override CodeViewTypeRecord? GetBaseType() { - return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Virtual base class {TypeIndex:X8} contains an invalid base type index {_baseTypeIndex:X8}."); @@ -47,7 +47,7 @@ public SerializedVBaseClassField( /// protected override CodeViewTypeRecord? GetBasePointerType() { - return _context.ParentImage.TryGetLeafRecord(_basePointerIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_basePointerIndex, out 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 index 66ed003c7..f9d5c9810 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs @@ -27,7 +27,7 @@ public SerializedVTableField(PdbReaderContext context, uint typeIndex, ref Binar /// protected override CodeViewTypeRecord? GetPointerType() { - return _context.ParentImage.TryGetLeafRecord(_pointerTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_pointerTypeIndex, out 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/StringIdentifier.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StringIdentifier.cs new file mode 100644 index 000000000..2de1b1bf2 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StringIdentifier.cs @@ -0,0 +1,89 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a String ID entry within the IPI stream of a PDB image. +/// +public class StringIdentifier : CodeViewLeaf, IIpiLeaf +{ + private readonly LazyVariable _value; + private readonly LazyVariable _subStrings; + + /// + /// Initializes an empty String ID entry. + /// + /// The type index. + protected StringIdentifier(uint typeIndex) + : base(typeIndex) + { + _value = new LazyVariable(GetValue); + _subStrings = new LazyVariable(GetSubStrings); + } + + /// + /// Creates a new String ID entry. + /// + /// The string value to wrap. + public StringIdentifier(Utf8String value) + : this(value, null) + { + } + + /// + /// Creates a new String ID entry. + /// + /// The string value to wrap. + /// A list of sub strings. + public StringIdentifier(Utf8String value, SubStringListLeaf? subStrings) + : base(0) + { + _value = new LazyVariable(value); + _subStrings = new LazyVariable(subStrings); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.StringId; + + /// + /// Gets or sets the wrapped string. + /// + public Utf8String Value + { + get => _value.Value; + set => _value.Value = value; + } + + /// + /// Gets or sets a list of sub strings associated to the entry (if available). + /// + public SubStringListLeaf? SubStrings + { + get => _subStrings.Value; + set => _subStrings.Value = value; + } + + /// + /// Obtains the wrapped string value. + /// + /// The string. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetValue() => null; + + /// + /// Obtains the sub strings associated to the string. + /// + /// The sub string. + /// + /// This method is called upon initialization of the property. + /// + protected virtual SubStringListLeaf? GetSubStrings() => null; + + /// + public override string ToString() + { + return SubStrings is not null + ? $"{Value} (Contains sub strings)" + : Value; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SubStringListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SubStringListLeaf.cs new file mode 100644 index 000000000..5a60730c4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SubStringListLeaf.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a list of sub strings associated to a String ID entry. +/// +public class SubStringListLeaf : CodeViewLeaf, IIpiLeaf +{ + private IList? _entries; + + /// + /// Initializes an empty sub-string list. + /// + /// The type index associated to the list. + protected SubStringListLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty list of sub-strings. + /// + public SubStringListLeaf() + : this(Enumerable.Empty()) + { + } + + /// + /// Creates a new list of sub-strings. + /// + /// The strings to add. + public SubStringListLeaf(params StringIdentifier[] elements) + : this(elements.AsEnumerable()) + { + } + + /// + /// Creates a new list of sub-strings. + /// + /// The strings to add. + public SubStringListLeaf(IEnumerable elements) + : base(0) + { + _entries = new List(elements); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.SubstrList; + + /// + /// Gets a collection of entries stored in the list. + /// + public IList Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the sub strings stored in the list. + /// + /// The sub strings. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs index 7fd7898f1..4894b62b6 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs @@ -3,7 +3,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Represents an unknown or unsupported CodeView type record. /// -public class UnknownCodeViewLeaf : CodeViewLeaf +public class UnknownCodeViewLeaf : CodeViewLeaf, ITpiLeaf, IIpiLeaf { /// /// Creates a new unknown type record. diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs index 508664c32..381d34e84 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs @@ -6,7 +6,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// /// Describes the shape of the virtual function table of a class or structure type. /// -public class VTableShapeLeaf : CodeViewLeaf +public class VTableShapeLeaf : CodeViewLeaf, ITpiLeaf { private IList? _entries; diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Modi/ModiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Modi/ModiStream.cs new file mode 100644 index 000000000..5f788fa54 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Modi/ModiStream.cs @@ -0,0 +1,144 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; + +namespace AsmResolver.Symbols.Pdb.Metadata.Modi; + +/// +/// Represents a single Module Info (MoDi) stream in a PDB image. +/// +public class ModiStream : SegmentBase +{ + private readonly LazyVariable _symbols; + private readonly LazyVariable _c11LineInfo; + private readonly LazyVariable _c13LineInfo; + private readonly LazyVariable _globalReferences; + + /// + /// Creates a new empty module info stream. + /// + public ModiStream() + { + _symbols = new LazyVariable(GetSymbols); + _c11LineInfo = new LazyVariable(GetC11LineInfo); + _c13LineInfo = new LazyVariable(GetC13LineInfo); + _globalReferences = new LazyVariable(GetGlobalReferences); + } + + /// + /// Gets or sets the signature the stream starts with. + /// + /// + /// This value is usually set to 4 by most compilers. + /// + public uint Signature + { + get; + set; + } = 4; + + /// + /// Gets or sets the sub-stream containing the CodeView symbol records defined in this module. + /// + public IReadableSegment? Symbols + { + get => _symbols.Value; + set => _symbols.Value = value; + } + + /// + /// Gets or sets the sub-stream containing the C11-style line information. + /// + public IReadableSegment? C11LineInfo + { + get => _c11LineInfo.Value; + set => _c11LineInfo.Value = value; + } + + /// + /// Gets or sets the sub-stream containing the C13-style line information. + /// + public IReadableSegment? C13LineInfo + { + get => _c13LineInfo.Value; + set => _c13LineInfo.Value = value; + } + + /// + /// Gets or sets the global references sub-stream. + /// + /// + /// The exact meaning of this sub-stream is not well understood. + /// + public IReadableSegment? GlobalReferences + { + get => _globalReferences.Value; + set => _globalReferences.Value = value; + } + + /// + /// Reads a Module Info stream from the provided input stream. + /// + /// The input stream to read from. + /// The module this modi stream is associated with. + /// The stream. + public static ModiStream FromReader(BinaryStreamReader reader, ModuleDescriptor module) + { + return new SerializedModiStream(reader, module); + } + + /// + /// Obtains the sub-stream containing the CodeView symbol records defined in this module. + /// + /// The sub-stream. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IReadableSegment? GetSymbols() => null; + + /// + /// Obtains the sub-stream containing the C11-style line information. + /// + /// The sub-stream. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IReadableSegment? GetC11LineInfo() => null; + + /// + /// Obtains the sub-stream containing the C13-style line information. + /// + /// The sub-stream. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IReadableSegment? GetC13LineInfo() => null; + + /// + /// Obtains the sub-stream containing the global references. + /// + /// The sub-stream. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IReadableSegment? GetGlobalReferences() => null; + + /// + public override uint GetPhysicalSize() + { + return sizeof(uint) + + (Symbols?.GetPhysicalSize() ?? 0) + + (C11LineInfo?.GetPhysicalSize() ?? 0) + + (C13LineInfo?.GetPhysicalSize() ?? 0) + + (GlobalReferences?.GetPhysicalSize() ?? 0); + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt32(Signature); + Symbols?.Write(writer); + C11LineInfo?.Write(writer); + C13LineInfo?.Write(writer); + GlobalReferences?.Write(writer); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Modi/SerializedModiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Modi/SerializedModiStream.cs new file mode 100644 index 000000000..f282d04cc --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Modi/SerializedModiStream.cs @@ -0,0 +1,72 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; + +namespace AsmResolver.Symbols.Pdb.Metadata.Modi; + +/// +/// Implements a Module Info (MoDi) stream that pulls its data from an input stream. +/// +public class SerializedModiStream : ModiStream +{ + private readonly BinaryStreamReader _reader; + private readonly uint _symbolSize; + private readonly uint _c11Size; + private readonly uint _c13Size; + + /// + /// Parses a Module Info stream from an input stream reader, and an accompanied module descriptor. + /// + /// The input stream. + /// The module descriptor this modi stream is attached to. + public SerializedModiStream(BinaryStreamReader reader, ModuleDescriptor module) + : this(reader, module.SymbolDataSize, module.SymbolC11DataSize, module.SymbolC13DataSize) + { + } + + /// + /// Parses a Module Info stream from an input stream reader, and an accompanied sub-stream sizes. + /// + /// The input stream. + /// The size of the symbol sub-stream. + /// The size of the C11-style line information sub-stream. + /// The size of the C13-style line information sub-stream. + public SerializedModiStream(BinaryStreamReader reader, uint symbolSize, uint c11Size, uint c13Size) + { + _symbolSize = symbolSize >= 4 + ? symbolSize - 4 + : 0; + + _c11Size = c11Size; + _c13Size = c13Size; + + Signature = reader.ReadUInt32(); + + _reader = reader; + } + + /// + protected override IReadableSegment? GetSymbols() => _symbolSize > 0 + ? _reader.Fork().ReadSegment(_symbolSize) + : null; + + /// + protected override IReadableSegment? GetC11LineInfo() => _c11Size > 0 + ? _reader.ForkRelative(_symbolSize).ReadSegment(_c11Size) + : null; + + /// + protected override IReadableSegment? GetC13LineInfo() => _c13Size > 0 + ? _reader.ForkRelative(_symbolSize + _c11Size).ReadSegment(_c13Size) + : null; + + /// + protected override IReadableSegment? GetGlobalReferences() + { + var reader = _reader.ForkRelative(_symbolSize + _c11Size + _c13Size); + + uint size = reader.ReadUInt32(); + return size > 0 + ? reader.ReadSegment(size) + : null; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs index f3a5440e3..1f7b18927 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs @@ -11,7 +11,12 @@ public abstract class TpiStream : SegmentBase /// /// Gets the default fixed MSF stream index for the TPI stream. /// - public const int StreamIndex = 2; + public const int TpiStreamIndex = 2; + + /// + /// Gets the default fixed MSF stream index for the IPI stream. + /// + public const int IpiStreamIndex = 4; internal const uint TpiStreamHeaderSize = sizeof(TpiStreamVersion) // Version diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs index 0abd232a9..391cc8db3 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -2,9 +2,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using AsmResolver.IO; +using AsmResolver.PE.File.Headers; using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; +using AsmResolver.Symbols.Pdb.Metadata.Info; using AsmResolver.Symbols.Pdb.Msf; using AsmResolver.Symbols.Pdb.Records; @@ -13,15 +17,87 @@ namespace AsmResolver.Symbols.Pdb; /// /// Represents a single Program Debug Database (PDB) image. /// -public class PdbImage +public class PdbImage : ICodeViewSymbolProvider { - private IList? _symbols; - private ConcurrentDictionary _simpleTypes = new(); + private readonly ConcurrentDictionary _simpleTypes = new(); + + private IList? _symbols; + private IList? _modules; + + /// + /// Gets or sets the time-stamp of the PDB file. + /// + public DateTime Timestamp + { + get; + set; + } + + /// + /// Gets or sets the number of times the PDB file has been written. + /// + public uint Age + { + get; + set; + } = 1; /// - /// Gets a collection of all symbols stored in the PDB image. + /// Gets or sets the unique identifier assigned to the PDB file. /// - public IList Symbols + public Guid UniqueId + { + get; + set; + } + + /// + /// Gets or sets the major version of the toolchain that was used to build the program. + /// + public byte BuildMajorVersion + { + get; + set; + } + + /// + /// Gets or sets the minor version of the toolchain that was used to build the program. + /// + public byte BuildMinorVersion + { + 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 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; + } + + /// + public IList Symbols { get { @@ -31,6 +107,19 @@ public IList Symbols } } + /// + /// Gets a collection of all modules stored in the PDB image. + /// + public IList Modules + { + get + { + if (_modules is null) + Interlocked.CompareExchange(ref _modules, GetModules(), null); + return _modules; + } + } + /// /// Reads a PDB image from the provided input file. /// @@ -81,12 +170,24 @@ public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParamete } /// - /// Attempts to obtain a type record from the TPI or IPI stream based on its type index. + /// Obtains all records stored in the original TPI stream of the PDB image. + /// + /// An object that lazily enumerates all TPI leaf records. + public virtual IEnumerable GetLeafRecords() => Enumerable.Empty(); + + /// + /// Obtains all records stored in the original IPI stream of the PDB image. + /// + /// An object that lazily enumerates all IPI leaf records. + public virtual IEnumerable GetIdLeafRecords() => Enumerable.Empty(); + + /// + /// Attempts to obtain a type record from the TPI 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) + public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out ITpiLeaf? leaf) { typeIndex &= 0x7fffffff; if (typeIndex is > 0 and < 0x1000) @@ -100,18 +201,105 @@ public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out Cod } /// - /// Obtains a type record from the TPI or IPI stream based on its type index. + /// Attempts to obtain a type record from the TPI stream based on its type index. + /// + /// The type index. + /// The resolved type. + /// true if the type was found, false otherwise. + public bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out TLeaf? leaf) + where TLeaf : ITpiLeaf + { + if (TryGetLeafRecord(typeIndex, out var x) && x is TLeaf resolved) + { + leaf = resolved; + return true; + } + + leaf = default; + return false; + } + + /// + /// Obtains a type record from the TPI stream based on its type index. /// /// The type index. /// The resolved type. /// Occurs when the type index is invalid. - public CodeViewLeaf GetLeafRecord(uint typeIndex) + public ITpiLeaf GetLeafRecord(uint typeIndex) { if (!TryGetLeafRecord(typeIndex, out var type)) throw new ArgumentException("Invalid type index."); return type; } + /// + /// Obtains a type record from the TPI stream based on its type index. + /// + /// The type index. + /// The resolved type. + /// Occurs when the type index is invalid. + public TLeaf GetLeafRecord(uint typeIndex) + where TLeaf : ITpiLeaf + { + return (TLeaf) GetLeafRecord(typeIndex); + } + + /// + /// Attempts to obtain an ID record from the IPI stream based on its ID index. + /// + /// The ID index. + /// The resolved leaf. + /// true if the leaf was found, false otherwise. + public virtual bool TryGetIdLeafRecord(uint idIndex, [NotNullWhen(true)] out IIpiLeaf? leaf) + { + leaf = null; + return false; + } + + /// + /// Attempts to obtain an ID record from the IPI stream based on its ID index. + /// + /// The ID index. + /// The resolved leaf. + /// true if the leaf was found, false otherwise. + public bool TryGetIdLeafRecord(uint idIndex, [NotNullWhen(true)] out TLeaf? leaf) + where TLeaf : IIpiLeaf + { + if (TryGetIdLeafRecord(idIndex, out var x) && x is TLeaf resolved) + { + leaf = resolved; + return true; + } + + leaf = default; + return false; + } + + /// + /// Obtains an ID record from the IPI stream based on its ID index. + /// + /// The ID index. + /// The resolved leaf + /// Occurs when the ID index is invalid. + public IIpiLeaf GetIdLeafRecord(uint idIndex) + { + if (!TryGetIdLeafRecord(idIndex, out var leaf)) + throw new ArgumentException("Invalid ID index."); + return leaf; + } + + /// + /// Obtains an ID record from the IPI stream based on its ID index. + /// + /// The ID index. + /// The resolved leaf + /// Occurs when the ID index is invalid. + public TLeaf GetIdLeafRecord(uint idIndex) + where TLeaf : CodeViewLeaf + { + return (TLeaf) GetIdLeafRecord(idIndex); + } + /// /// Obtains a collection of symbols stored in the symbol record stream of the PDB image. /// @@ -119,5 +307,14 @@ public CodeViewLeaf GetLeafRecord(uint typeIndex) /// /// This method is called upon initialization of the property. /// - protected virtual IList GetSymbols() => new List(); + protected virtual IList GetSymbols() => new List(); + + /// + /// Obtains a collection of modules stored in the DBI stream of the PDB image. + /// + /// The modules. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetModules() => new List(); } diff --git a/src/AsmResolver.Symbols.Pdb/PdbModule.cs b/src/AsmResolver.Symbols.Pdb/PdbModule.cs new file mode 100644 index 000000000..bd76dae1c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbModule.cs @@ -0,0 +1,158 @@ +using System.Collections.Generic; +using System.Threading; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; +using AsmResolver.Symbols.Pdb.Records; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Represents a single module stored in a PDB image. +/// +public class PdbModule : ICodeViewSymbolProvider +{ + private readonly LazyVariable _name; + private readonly LazyVariable _objectFileName; + private IList? _sourceFiles; + private readonly LazyVariable _sectionContribution; + + private IList? _symbols; + + /// + /// Initialize an empty module in a PDB image. + /// + protected PdbModule() + { + _name = new LazyVariable(GetName); + _objectFileName = new LazyVariable(GetObjectFileName); + _sectionContribution = new LazyVariable(GetSectionContribution); + } + + /// + /// Creates a new linked module in a PDB image. + /// + /// The name of the module. + public PdbModule(Utf8String name) + : this(name, name) + { + } + + /// + /// Creates a new module in a PDB image that was constructed from an object file. + /// + /// The name of the module. + /// The path to the object file. + public PdbModule(Utf8String name, Utf8String objectFileName) + { + _name = new LazyVariable(name); + _objectFileName = new LazyVariable(objectFileName); + _sectionContribution = new LazyVariable(new SectionContribution()); + } + + /// + /// 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? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// 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 => _objectFileName.Value; + set => _objectFileName.Value = value; + } + + /// + /// Gets a collection of source files that were used to compile the module. + /// + public IList SourceFiles + { + get + { + if (_sourceFiles is null) + Interlocked.CompareExchange(ref _sourceFiles, GetSourceFiles(), null); + return _sourceFiles; + } + } + + /// + /// 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 => _sectionContribution.Value; + set => _sectionContribution.Value = value; + } + + /// + public IList Symbols + { + get + { + if (_symbols is null) + Interlocked.CompareExchange(ref _symbols, GetSymbols(), null); + return _symbols; + } + } + + /// + /// Obtains the name of the module. + /// + /// The name. + /// + /// This method is called upon the initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + /// Obtains the object file name of the module. + /// + /// The object file name. + /// + /// This method is called upon the initialization of the property. + /// + protected virtual Utf8String? GetObjectFileName() => null; + + /// + /// Obtains the source file names associated to the module. + /// + /// The source file names. + /// + /// This method is called upon the initialization of the property. + /// + protected virtual IList GetSourceFiles() => new List(); + + /// + /// Obtains the section contribution of the module. + /// + /// The section contribution. + /// + /// This method is called upon the initialization of the property. + /// + protected virtual SectionContribution? GetSectionContribution() => new(); + + /// + /// Obtains the symbols stored in the module. + /// + /// The symbols. + /// + /// This method is called upon the initialization of the property. + /// + protected virtual IList GetSymbols() => new List(); + + /// + public override string ToString() => Name ?? ObjectFileName ?? "<<>>"; +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbSourceFile.cs b/src/AsmResolver.Symbols.Pdb/PdbSourceFile.cs new file mode 100644 index 000000000..fb9dc3166 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbSourceFile.cs @@ -0,0 +1,28 @@ +namespace AsmResolver.Symbols.Pdb; + +/// +/// Represents a single source file that was used to compile a module or compiland in a binary. +/// +public class PdbSourceFile +{ + /// + /// Creates a new PDB source file. + /// + /// The file name of the source file. + public PdbSourceFile(Utf8String fileName) + { + FileName = fileName; + } + + /// + /// Gets or sets the file name of the source file. + /// + public Utf8String FileName + { + get; + set; + } + + /// + public override string ToString() => FileName; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/BasePointerRelativeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/BasePointerRelativeSymbol.cs new file mode 100644 index 000000000..83bb21064 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/BasePointerRelativeSymbol.cs @@ -0,0 +1,85 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol that is defined by an offset relative to the base pointer register. +/// +public class BasePointerRelativeSymbol : CodeViewSymbol, IRegisterRelativeSymbol +{ + private readonly LazyVariable _variableType; + private readonly LazyVariable _name; + + /// + /// Initializes an empty base-pointer relative symbol. + /// + protected BasePointerRelativeSymbol() + { + _name = new LazyVariable(GetName); + _variableType = new LazyVariable(GetVariableType); + } + + /// + /// Creates a new base-pointer relative symbol. + /// + /// The name of the symbol. + /// The offset relative to the base pointer. + /// The type of variable the symbol stores. + public BasePointerRelativeSymbol(Utf8String name, CodeViewTypeRecord variableType, int offset) + { + _name = new LazyVariable(name); + _variableType = new LazyVariable(variableType); + Offset = offset; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.BPRel32; + + /// + /// Gets or sets the offset relative to the base pointer. + /// + public int Offset + { + get; + set; + } + + /// + /// Gets or sets the type of values the symbol stores. + /// + public CodeViewTypeRecord? VariableType + { + get => _variableType.Value; + set => _variableType.Value = value; + } + + /// + /// Gets or sets the name of the symbol. + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the variable type of the symbol. + /// + /// The variable type + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetVariableType() => null; + + /// + /// Obtains the name of the symbol. + /// + /// The name + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_BPREL32 [{Offset:X}]: {VariableType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/BinaryAnnotation.cs b/src/AsmResolver.Symbols.Pdb/Records/BinaryAnnotation.cs new file mode 100644 index 000000000..f6da0586f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/BinaryAnnotation.cs @@ -0,0 +1,20 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a single binary annotation within an inline call site. +/// +/// The opcode. +/// The first operand. +/// The second operand (if applicable). +public record struct BinaryAnnotation(BinaryAnnotationOpCode OpCode, uint Operand1, uint Operand2) +{ + /// + /// Gets the number of operands this binary annotation contains. + /// + public int OperandCount => OpCode == BinaryAnnotationOpCode.ChangeCodeLengthAndCodeOffset ? 2 : 1; + + /// + public override string ToString() => OperandCount == 1 + ? $"{OpCode} {Operand1:X}" + : $"{OpCode} {Operand1:X} {Operand2:X}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/BinaryAnnotationOpCode.cs b/src/AsmResolver.Symbols.Pdb/Records/BinaryAnnotationOpCode.cs new file mode 100644 index 000000000..f0bd6fe8f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/BinaryAnnotationOpCode.cs @@ -0,0 +1,24 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members describing all possible opcodes that a binary annotation can use. +/// +public enum BinaryAnnotationOpCode +{ +#pragma warning disable CS1591 + Illegal, + CodeOffset, + ChangeCodeOffsetBase, + ChangeCodeOffset, + ChangeCodeLength, + ChangeFile, + ChangeLineOffset, + ChangeLineEndDelta, + ChangeRangeKind, + ChangeColumnStart, + ChangeColumnEndDelta, + ChangeCodeOffsetAndLineOffset, + ChangeCodeLengthAndCodeOffset, + ChangeColumnEnd, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/BuildInfoSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/BuildInfoSymbol.cs new file mode 100644 index 000000000..60ed99b80 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/BuildInfoSymbol.cs @@ -0,0 +1,49 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol containing build information for a file in a PDB image. +/// +public class BuildInfoSymbol : CodeViewSymbol +{ + private readonly LazyVariable _info; + + /// + /// Initializes an empty build information symbol. + /// + protected BuildInfoSymbol() + { + _info = new LazyVariable(GetInfo); + } + + /// + /// Wraps a build information leaf into a symbol. + /// + /// The information to wrap. + public BuildInfoSymbol(BuildInfoLeaf info) + { + _info = new LazyVariable(info); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.BuildInfo; + + /// + /// Gets or sets the information that is wrapped into a symbol. + /// + public BuildInfoLeaf? Info + { + get => _info.Value; + set => _info.Value = value; + } + + /// + /// Obtains the wrapped build information. + /// + /// The information. + /// + /// This method is called upon initialization of the property. + /// + protected virtual BuildInfoLeaf? GetInfo() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/CallSiteSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CallSiteSymbol.cs new file mode 100644 index 000000000..a2eb2e41e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CallSiteSymbol.cs @@ -0,0 +1,74 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides information about the signature of an indirect call. +/// +public class CallSiteSymbol : CodeViewSymbol +{ + private readonly LazyVariable _functionType; + + /// + /// Initializes an empty call site symbol. + /// + protected CallSiteSymbol() + { + _functionType = new LazyVariable(GetFunctionType); + } + + /// + /// Creates a new call site info symbol. + /// + /// The index of the section the call resides in. + /// The offset to the call. + /// The type describing the shape of the function. + public CallSiteSymbol(ushort sectionIndex, int offset, CodeViewTypeRecord functionType) + { + SectionIndex = sectionIndex; + Offset = offset; + _functionType = new LazyVariable(functionType); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.CallSiteInfo; + + /// + /// Gets or sets the index of the section the call resides in. + /// + public ushort SectionIndex + { + get; + set; + } + + /// + /// Gets or sets the offset to the call. + /// + public int Offset + { + get; + set; + } + + /// + /// Gets or sets the type describing the shape of the function that is called. + /// + public CodeViewTypeRecord? FunctionType + { + get => _functionType.Value; + set => _functionType.Value = value; + } + + /// + /// Obtains the function type of the call. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetFunctionType() => null; + + /// + public override string ToString() => $"S_CALLSITEINFO [{SectionIndex:X4}:{Offset:X8}]: {FunctionType}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs index 21d6191af..0fbd249f4 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs @@ -1,12 +1,13 @@ using AsmResolver.IO; using AsmResolver.Symbols.Pdb.Records.Serialized; +using static AsmResolver.Symbols.Pdb.Records.CodeViewSymbolType; namespace AsmResolver.Symbols.Pdb.Records; /// /// Represents a single symbol record within the symbol record stream of a PDB file. /// -public abstract class CodeViewSymbol +public abstract class CodeViewSymbol : ICodeViewSymbol { /// /// Gets the type of symbol this record encodes. @@ -31,12 +32,46 @@ public static CodeViewSymbol FromReader(PdbReaderContext context, ref BinaryStre return type switch { - CodeViewSymbolType.Pub32 => new SerializedPublicSymbol(dataReader), - CodeViewSymbolType.Udt => new SerializedUserDefinedTypeSymbol(context, dataReader), - CodeViewSymbolType.Constant => new SerializedConstantSymbol(context, dataReader), - CodeViewSymbolType.ProcRef => new SerializedProcedureReferenceSymbol(dataReader, false), - CodeViewSymbolType.LProcRef => new SerializedProcedureReferenceSymbol(dataReader, true), + BPRel32 => new SerializedBasePointerRelativeSymbol(context, dataReader), + BuildInfo => new SerializedBuildInfoSymbol(context, dataReader), + CallSiteInfo => new SerializedCallSiteSymbol(context, dataReader), + Callees => new SerializedFunctionListSymbol(context, dataReader, false), + Callers => new SerializedFunctionListSymbol(context, dataReader, true), + CoffGroup => new SerializedCoffGroup(dataReader), + Compile2 => new SerializedCompile2Symbol(dataReader), + Compile3 => new SerializedCompile3Symbol(dataReader), + Constant => new SerializedConstantSymbol(context, dataReader), + DefRangeFramePointerRel => new SerializedFramePointerRangeSymbol(dataReader, false), + DefRangeFramePointerRelFullScope => new SerializedFramePointerRangeSymbol(dataReader, true), + DefRangeRegister => new SerializedRegisterRangeSymbol(dataReader), + DefRangeRegisterRel => new SerializedRegisterRelativeRangeSymbol(dataReader), + EnvBlock => new SerializedEnvironmentBlockSymbol(dataReader), + FileStatic => new SerializedFileStaticSymbol(context, dataReader), + FrameCookie => new SerializedFrameCookieSymbol(dataReader), + FrameProc => new SerializedFrameProcedureSymbol(dataReader), + GData32 => new SerializedDataSymbol(context, dataReader, true), + GProc32 => new SerializedProcedureSymbol(context, dataReader, true, false), + GProc32Id => new SerializedProcedureSymbol(context, dataReader, true, true), + InlineSite => new SerializedInlineSiteSymbol(context, dataReader), + Label32 => new SerializedLabelSymbol(dataReader), + Local => new SerializedLocalSymbol(context, dataReader), + LData32 => new SerializedDataSymbol(context, dataReader, false), + LProc32 => new SerializedProcedureSymbol(context, dataReader, false, false), + LProc32Id => new SerializedProcedureSymbol(context, dataReader, false, true), + LProcRef => new SerializedProcedureReferenceSymbol(dataReader, true), + ObjName => new SerializedObjectNameSymbol(dataReader), + ProcRef => new SerializedProcedureReferenceSymbol(dataReader, false), + Pub32 => new SerializedPublicSymbol(dataReader), + Register => new SerializedRegisterSymbol(context, dataReader), + RegRel32 => new SerializedRegisterRelativeSymbol(context, dataReader), + Section => new SerializedSectionSymbol(dataReader), + Thunk32 => new SerializedThunkSymbol(context, dataReader), + Udt => new SerializedUserDefinedTypeSymbol(context, dataReader), + UNamespace => new SerializedUsingNamespaceSymbol(dataReader), _ => new UnknownSymbol(type, dataReader.ReadToEnd()) }; } + + /// + public override string ToString() => $"S_{CodeViewSymbolType.ToString().ToUpper()}"; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs index 11bb6e3ad..c22ff04da 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs @@ -51,7 +51,7 @@ public enum CodeViewSymbolType : ushort /// /// Indicates the symbol is a path to object file name /// - ObjnameSt = 0x0009, + ObjNameSt = 0x0009, /// /// Indicates the symbol is a end of argument/return list @@ -86,7 +86,7 @@ public enum CodeViewSymbolType : ushort /// /// Indicates the symbol is a Module-local symbol /// - Ldata16 = 0x0101, + LData16 = 0x0101, /// /// Indicates the symbol is a Global data symbol @@ -418,7 +418,7 @@ public enum CodeViewSymbolType : ushort /// /// Reserved /// - RESERVED4 = 0x101f, + Reserved4 = 0x101f, LManDataSt = 0x1020, @@ -505,7 +505,7 @@ public enum CodeViewSymbolType : ushort /// /// Indicates the symbol is a BP-relative /// - BBRel32 = 0x110b, + BPRel32 = 0x110b, /// /// Indicates the symbol is a Module-local symbol @@ -570,12 +570,12 @@ public enum CodeViewSymbolType : ushort /// /// Indicates the symbol is a Local procedure start (IA64) /// - LprocIa64 = 0x1118, + LProcIA64 = 0x1118, /// /// Indicates the symbol is a Global procedure start (IA64) /// - GProcIa64 = 0x1119, + GProcIA64 = 0x1119, /// /// Indicates the symbol is a local IL sym with field for local slot index diff --git a/src/AsmResolver.Symbols.Pdb/Records/CoffGroupSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CoffGroupSymbol.cs new file mode 100644 index 000000000..6f04d1c82 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CoffGroupSymbol.cs @@ -0,0 +1,96 @@ +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a single Common Object File Format (COFF) group symbol. +/// +public class CoffGroupSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + + /// + /// Initializes an empty COFF group symbol. + /// + protected CoffGroupSymbol() + { + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new COFF group symbol. + /// + /// The name of the group. + /// The index of the segment the group resides in. + /// The offset within the segment. + /// The size of the group in bytes. + /// The characteristics describing the group. + public CoffGroupSymbol(Utf8String name, ushort segmentIndex, uint offset, uint size, SectionFlags characteristics) + { + _name = new LazyVariable(name); + SegmentIndex = segmentIndex; + Offset = offset; + Size = size; + Characteristics = characteristics; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.CoffGroup; + + /// + /// Gets or sets the size of the group in bytes. + /// + public uint Size + { + get; + set; + } + + /// + /// Gets or sets the characteristics describing the group. + /// + public SectionFlags Characteristics + { + get; + set; + } + + /// + /// Gets or sets the index of the segment the group resides in. + /// + public ushort SegmentIndex + { + get; + set; + } + + /// + /// Gets or sets the offset within the segment the group starts at. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets the name of the group. + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the group. + /// + /// The name + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_COFFGROUP: [{SegmentIndex:X4}:{Offset:X8}], Cb: {Size:X8}, Name: {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Compile2Symbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Compile2Symbol.cs new file mode 100644 index 000000000..a56e061c6 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Compile2Symbol.cs @@ -0,0 +1,10 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a compile symbol that uses version 2 of the file format. +/// +public class Compile2Symbol : CompileSymbol +{ + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Compile2; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Compile3Symbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Compile3Symbol.cs new file mode 100644 index 000000000..562acd4c6 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Compile3Symbol.cs @@ -0,0 +1,29 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a compile symbol that uses version 3 of the file format. +/// +public class Compile3Symbol : CompileSymbol +{ + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Compile3; + + /// + /// Gets or sets the front-end QFE version of the file. + /// + public ushort FrontEndQfeVersion + { + get; + set; + } + + /// + /// Gets or sets the front-end QFE version of the file. + /// + public ushort BackEndQfeVersion + { + get; + set; + } +} + diff --git a/src/AsmResolver.Symbols.Pdb/Records/CompileAttributes.cs b/src/AsmResolver.Symbols.Pdb/Records/CompileAttributes.cs new file mode 100644 index 000000000..74dc425d9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CompileAttributes.cs @@ -0,0 +1,75 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members describing all possible flags that can be assigned to a . +/// +[Flags] +public enum CompileAttributes +{ + /// + /// Indicates the file is compiled without any extra flags. + /// + None = 0, + + /// + /// Indicates the file is compiled for Edit-and-Continue. + /// + EC = 1 << 0, + + /// + /// Indicates the file is not compiled with debug info. + /// + NoDbgInfo = 1 << 1, + + /// + /// Indicates the file is compiled with LTCG. + /// + Ltcg = 1 << 2, + + /// + /// Indicates the file is compiled with -Bzalign. + /// + NoDataAlign = 1 << 3, + + /// + /// Indicates managed code/data is present in the file. + /// + ManagedPresent = 1 << 4, + + /// + /// Indicates the file is compiled with /GS. + /// + SecurityChecks = 1 << 5, + + /// + /// Indicates the file is compiled with /hotpatch. + /// + HotPatch = 1 << 6, + + /// + /// Indicates the file is converted with CVTCIL. + /// + CvtCil = 1 << 7, + + /// + /// Indicates the file is a MSIL netmodule. + /// + MsilModule = 1 << 8, + + /// + /// Indicates the file is compiled with /sdl. + /// + Sdl = 1 << 9, + + /// + /// Indicates the file is compiled with /ltcg:pgo or pgu. + /// + Pgo = 1 << 10, + + /// + /// Indicates the file is a .exp module. + /// + Exp = 1 << 11 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/CompileSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CompileSymbol.cs new file mode 100644 index 000000000..cb95e2b29 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CompileSymbol.cs @@ -0,0 +1,116 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol that describes information about the compiler that was used to compile the file. +/// +public abstract class CompileSymbol : CodeViewSymbol +{ + private readonly LazyVariable _compilerVersion; + + /// + /// Initializes an empty compile symbol. + /// + protected CompileSymbol() + { + _compilerVersion = new LazyVariable(GetCompilerVersion); + } + + /// + /// Gets or sets the identifier of the language that was used to compile the file. + /// + public SourceLanguage Language + { + get; + set; + } + + /// + /// Gets or sets attributes associated to the compile symbol. + /// + public CompileAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the architecture the file is targeting. + /// + public CpuType Machine + { + get; + set; + } + + /// + /// Gets or sets the front-end major version of the file. + /// + public ushort FrontEndMajorVersion + { + get; + set; + } + + /// + /// Gets or sets the front-end minor version of the file. + /// + public ushort FrontEndMinorVersion + { + get; + set; + } + + /// + /// Gets or sets the front-end build version of the file. + /// + public ushort FrontEndBuildVersion + { + get; + set; + } + + /// + /// Gets or sets the back-end major version of the file. + /// + public ushort BackEndMajorVersion + { + get; + set; + } + + /// + /// Gets or sets the back-end minor version of the file. + /// + public ushort BackEndMinorVersion + { + get; + set; + } + + /// + /// Gets or sets the back-end build version of the file. + /// + public ushort BackEndBuildVersion + { + get; + set; + } + + /// + /// Gets or sets the version of the compiler that was used to compile the file. + /// + public Utf8String CompilerVersion + { + get => _compilerVersion.Value; + set => _compilerVersion.Value = value; + } + + /// + /// Obtains the compiler version string. + /// + /// The compiler version. + /// + /// This method is called upon the initialization of the property. + /// + protected virtual Utf8String GetCompilerVersion() => Utf8String.Empty; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs index 5bdc45c4c..dffc6183a 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs @@ -81,5 +81,5 @@ public Utf8String Name protected virtual CodeViewTypeRecord? GetConstantType() => null; /// - public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name} = {Value}"; + public override string ToString() => $"S_CONSTANT: {Type} {Name} = {Value}"; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/CpuType.cs b/src/AsmResolver.Symbols.Pdb/Records/CpuType.cs new file mode 100644 index 000000000..031d0ca99 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CpuType.cs @@ -0,0 +1,73 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members for all possible target CPUs that an executable file can target. +/// +public enum CpuType : ushort +{ +#pragma warning disable CS1591 + Intel8080 = 0x0, + Intel8086 = 0x1, + Intel80286 = 0x2, + Intel80386 = 0x3, + Intel80486 = 0x4, + Pentium = 0x5, + PentiumPro = 0x6, + Pentium3 = 0x7, + Mips = 0x10, + Mips16 = 0x11, + Mips32 = 0x12, + Mips64 = 0x13, + MipsI = 0x14, + MipsII = 0x15, + MipsIII = 0x16, + MipsIV = 0x17, + MipsV = 0x18, + M68000 = 0x20, + M68010 = 0x21, + M68020 = 0x22, + M68030 = 0x23, + M68040 = 0x24, + Alpha = 0x30, + Alpha21164 = 0x31, + Alpha21164A = 0x32, + Alpha21264 = 0x33, + Alpha21364 = 0x34, + Ppc601 = 0x40, + Ppc603 = 0x41, + Ppc604 = 0x42, + Ppc620 = 0x43, + PpcFP = 0x44, + PpcBE = 0x45, + SH3 = 0x50, + SH3E = 0x51, + SH3DSP = 0x52, + SH4 = 0x53, + SHMedia = 0x54, + Arm3 = 0x60, + Arm4 = 0x61, + Arm4T = 0x62, + Arm5 = 0x63, + Arm5T = 0x64, + Arm6 = 0x65, + ArmXmac = 0x66, + ArmWmmx = 0x67, + Arm7 = 0x68, + Omni = 0x70, + IA64 = 0x80, + IA64_2 = 0x81, + Cee = 0x90, + AM33 = 0xa0, + M32R = 0xb0, + TriCore = 0xc0, + X64 = 0xd0, + EBC = 0xe0, + Thumb = 0xf0, + ArmNT = 0xf4, + Arm64 = 0xf6, + HybridX86Arm64 = 0xf7, + Arm64EC = 0xf8, + Arm64X = 0xf9, + D3D11Shader = 0x100, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/DataSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/DataSymbol.cs new file mode 100644 index 000000000..e2c29c5cb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/DataSymbol.cs @@ -0,0 +1,111 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a global or local data symbol. +/// +public class DataSymbol : CodeViewSymbol, IVariableSymbol +{ + private readonly LazyVariable _name; + private readonly LazyVariable _variableType; + + /// + /// Initializes an empty data symbol. + /// + protected DataSymbol() + { + _name = new LazyVariable(GetName); + _variableType = new LazyVariable(GetVariableType); + } + + /// + /// Creates a new named data symbol. + /// + /// The name of the symbol. + /// The data type of the symbol. + public DataSymbol(Utf8String name, CodeViewTypeRecord variableType) + { + _name = new LazyVariable(name); + _variableType = new LazyVariable(variableType); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => IsGlobal + ? CodeViewSymbolType.GData32 + : CodeViewSymbolType.LData32; + + /// + /// Gets or sets a value indicating whether the symbol is a global data symbol. + /// + public bool IsGlobal + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether the symbol is a local data symbol. + /// + public bool IsLocal + { + get => !IsGlobal; + set => IsGlobal = !value; + } + + /// + /// Gets or sets the file segment index this symbol is located in. + /// + public ushort SegmentIndex + { + get; + set; + } + + /// + /// Gets or sets the offset within the file that this symbol is defined at. + /// + public uint Offset + { + get; + set; + } + + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + public CodeViewTypeRecord? VariableType + { + get => _variableType.Value; + set => _variableType.Value = value; + } + + /// + /// Obtains the name of the symbol. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + /// Obtains the type of the variable. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetVariableType() => null; + + /// + public override string ToString() + { + return $"S_{CodeViewSymbolType.ToString().ToUpper()}: [{SegmentIndex:X4}:{Offset:X8}] {VariableType} {Name}"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/DefinitionRangeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/DefinitionRangeSymbol.cs new file mode 100644 index 000000000..fa53ebc70 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/DefinitionRangeSymbol.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a definition range for a symbol. +/// +public abstract class DefinitionRangeSymbol : CodeViewSymbol +{ + private IList? _gaps; + + /// + /// Initializes an empty def-range. + /// + protected DefinitionRangeSymbol() + { + } + + /// + /// Initializes the def-range with the provided address range and gaps. + /// + /// The address range. + /// The gaps within the address range the symbol is invalid at. + protected DefinitionRangeSymbol(LocalAddressRange range, IEnumerable gaps) + { + Range = range; + _gaps = new List(gaps); + } + + /// + /// Gets a collection of gaps in where this symbol is not available. + /// + public IList Gaps + { + get + { + if (_gaps is null) + Interlocked.CompareExchange(ref _gaps, GetGaps(), null); + return _gaps; + } + } + + /// + /// Gets or sets the range of addresses within the program where this symbol is valid. + /// + public LocalAddressRange Range + { + get; + set; + } + + /// + /// Obtains the collection of ranges the symbol is unavailable. + /// + /// The gaps. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetGaps() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/EnvironmentBlockSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/EnvironmentBlockSymbol.cs new file mode 100644 index 000000000..4978d4868 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/EnvironmentBlockSymbol.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a list of key-value pairs describing environment variables and their values that were used during the +/// compilation of a module. +/// +public class EnvironmentBlockSymbol : CodeViewSymbol +{ + private IList>? _entries; + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.EnvBlock; + + /// + /// Gets a collection of key-value pairs with all the environment variables and their assigned values. + /// + public IList> Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the environment variables and their values. + /// + /// The name-value pairs. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList> GetEntries() => new List>(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/FileStaticSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/FileStaticSymbol.cs new file mode 100644 index 000000000..a0a408b52 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/FileStaticSymbol.cs @@ -0,0 +1,91 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a file static variable symbol. +/// +public class FileStaticSymbol : CodeViewSymbol, IVariableSymbol +{ + private readonly LazyVariable _name; + private readonly LazyVariable _variableType; + + /// + /// Initializes an empty file static symbol. + /// + protected FileStaticSymbol() + { + _name = new LazyVariable(GetName); + _variableType = new LazyVariable(GetVariableType); + } + + /// + /// Creates a new file static variable symbol. + /// + /// The name of the variable. + /// The data type of the variable. + /// The attributes describing the variable. + public FileStaticSymbol(Utf8String name, CodeViewTypeRecord variableType, LocalAttributes attributes) + { + Attributes = attributes; + _name = new LazyVariable(name); + _variableType = new LazyVariable(variableType); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.FileStatic; + + /// + /// Gets or sets the index of the module's file name within the string table. + /// + public uint ModuleFileNameOffset + { + get; + set; + } + + /// + /// Gets or sets the attributes describing the variable. + /// + public LocalAttributes Attributes + { + get; + set; + } + + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + public CodeViewTypeRecord? VariableType + { + get => _variableType.Value; + set => _variableType.Value = value; + } + + /// + /// Obtains the type of the variable. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetVariableType() => null; + + /// + /// Obtains the name of the variable. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_FILESTATIC: {VariableType} {Name}"; + +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/FrameCookieSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/FrameCookieSymbol.cs new file mode 100644 index 000000000..b627eb503 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/FrameCookieSymbol.cs @@ -0,0 +1,74 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Describes the position and type of a security cookie within a stack frame. +/// +public class FrameCookieSymbol : CodeViewSymbol +{ + /// + /// Initializes an empty frame cookie symbol. + /// + protected FrameCookieSymbol() + { + } + + /// + /// Creates a new frame cookie symbol. + /// + /// The offset relative to the frame pointer. + /// The register storing the cookie. + /// The type of cookie that is computed. + /// The attributes associated to the cookie. + public FrameCookieSymbol(int frameOffset, ushort register, FrameCookieType cookieType, byte attributes) + { + FrameOffset = frameOffset; + Register = register; + CookieType = cookieType; + Attributes = attributes; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.FrameCookie; + + /// + /// Gets or sets the offset relative to the frame pointer where the cookie is stored. + /// + public int FrameOffset + { + get; + set; + } + + /// + /// Gets or sets the register storing the cookie. + /// + public ushort Register + { + get; + set; + } + + /// + /// Gets or sets the type of cookie that is computed. + /// + public FrameCookieType CookieType + { + get; + set; + } + + /// + /// Gets or sets attributes describing the cookie. + /// + /// + /// This flags field is not well understood. + /// + public byte Attributes + { + get; + set; + } + + /// + public override string ToString() => $"S_FRAMECOOKIE: {CookieType} +{FrameOffset:X}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/FrameCookieType.cs b/src/AsmResolver.Symbols.Pdb/Records/FrameCookieType.cs new file mode 100644 index 000000000..b07c3b4dd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/FrameCookieType.cs @@ -0,0 +1,30 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members defining all possible stack frame cookie types. +/// +// NOTE: Even though cvinfo.h doesn't explicitly specify an element type (and thus it would be an int according to C +// specification), in practice a uint8_t is written by compilers instead to represent the cookie type. CVDump.exe +// actually is bugged and reads arbitrary memory when dumping symbol information from a PDB file. +public enum FrameCookieType : byte +{ + /// + /// Indicates the cookie is stored as a simple plain copy. + /// + Copy = 0, + + /// + /// Indicates the cookie is stored after XOR'ing it with the stack pointer. + /// + XorSP, + + /// + /// Indicates the cookie is stored after XOR'ing it with the base pointer. + /// + XorBP, + + /// + /// Indicates the cookie is stored after XOR'ing it with R13. + /// + XorR13 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/FramePointerRangeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/FramePointerRangeSymbol.cs new file mode 100644 index 000000000..faa644ffd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/FramePointerRangeSymbol.cs @@ -0,0 +1,75 @@ +using System.Linq; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol def-range that is stored at an offset relative to the frame pointer. +/// +public class FramePointerRangeSymbol : DefinitionRangeSymbol +{ + private bool _isFullScope; + + /// + /// Initializes an empty frame-pointer def-range. + /// + protected FramePointerRangeSymbol() + { + } + + /// + /// Creates a new frame-pointer def-range. + /// + /// The offset the symbol is declared at, relative to the frame pointer. + /// The address range the symbol is valid in. + public FramePointerRangeSymbol(int offset, LocalAddressRange range) + : base(range, Enumerable.Empty()) + { + Offset = offset; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => IsFullScope + ? CodeViewSymbolType.DefRangeFramePointerRelFullScope + : CodeViewSymbolType.DefRangeFramePointerRel; + + /// + /// Gets or sets a value indicating whether the symbol spans the full scope of the function or not. + /// + /// + /// When this value is set to true, the values in and + /// have no meaning. + /// + public bool IsFullScope + { + get => _isFullScope; + set + { + _isFullScope = value; + + if (value) + { + Range = default; + Gaps.Clear(); + } + } + } + + /// + /// Gets or sets the offset the symbol is declared at, relative to the frame pointer. + /// + public int Offset + { + get; + set; + } + + /// + public override string ToString() + { + string prefix = IsFullScope + ? "S_DEFRANGE_FRAMEPOINTER_FULLSCOPE" + : "S_DEFRANGE_FRAMEPOINTER"; + + return $"{prefix}: [{Offset:X}] Range: {Range} (Gap Count: {Gaps.Count})"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/FrameProcedureAttributes.cs b/src/AsmResolver.Symbols.Pdb/Records/FrameProcedureAttributes.cs new file mode 100644 index 000000000..9dc605378 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/FrameProcedureAttributes.cs @@ -0,0 +1,115 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members defining all possible flags that can be assigned to a procedure. +/// +[Flags] +public enum FrameProcedureAttributes : uint +{ + /// + /// Indicates the function uses _alloca(). + /// + HasAlloca = 1 << 0, + + /// + /// Indicates the function uses setjmp(). + /// + HasSetJmp = 1 << 1, + + /// + /// Indicates the function uses longjmp(). + /// + HasLongJmp = 1 << 2, + + /// + /// Indicates the function uses inline asm. + /// + HasInlAsm = 1 << 3, + + /// + /// Indicates the function has EH states. + /// + HasEH = 1 << 4, + + /// + /// Indicates the function was speced as inline. + /// + InlSpec = 1 << 5, + + /// + /// Indicates the function has SEH. + /// + HasSEH = 1 << 6, + + /// + /// Indicates the function is __declspec(naked). + /// + Naked = 1 << 7, + + /// + /// Indicates the function has buffer security check introduced by /GS. + /// + SecurityChecks = 1 << 8, + + /// + /// Indicates the function is compiled with /EHa. + /// + AsyncEH = 1 << 9, + + /// + /// Indicates the function has /GS buffer checks, but stack ordering couldn't be done. + /// + GSNoStackOrdering = 1 << 10, + + /// + /// Indicates the function was inlined within another function. + /// + WasInlined = 1 << 11, + + /// + /// Indicates the function is __declspec(strict_gs_check). + /// + GSCheck = 1 << 12, + + /// + /// Indicates the function is __declspec(safebuffers). + /// + SafeBuffers = 1 << 13, + + /// + /// Indicates the record function's local pointer explicitly. + /// + EncodedLocalBasePointerMask = (1 << 14) | (1 << 15), + + /// + /// Indicates the record function's parameter pointer explicitly. + /// + EncodedParamBasePointerMask = (1 << 16) | (1 << 17), + + /// + /// Indicates the function was compiled with PGO/PGU. + /// + PogoOn = 1 << 18, + + /// + /// Indicates the symbol has valid POGO counts. + /// + ValidCounts = 1 << 19, + + /// + /// Indicates the symbol was optimized for speed. + /// + OptSpeed = 1 << 20, + + /// + /// Indicates the function contains CFG checks (and no write checks). + /// + GuardCF = 1 << 21, + + /// + /// Indicates the function contains CFW checks and/or instrumentation. + /// + GuardCFW = 1 << 22, +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/FrameProcedureSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/FrameProcedureSymbol.cs new file mode 100644 index 000000000..e004b779f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/FrameProcedureSymbol.cs @@ -0,0 +1,111 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides extra information about a procedure and its frame layout. +/// +public class FrameProcedureSymbol : CodeViewSymbol +{ + /// + /// Initializes an empty frame procedure symbol. + /// + protected FrameProcedureSymbol() + { + } + + /// + /// Creates a new frame procedure symbol. + /// + /// The size of the frame. + /// The attributes associated to the procedure. + public FrameProcedureSymbol(uint frameSize, FrameProcedureAttributes attributes) + { + FrameSize = frameSize; + Attributes = attributes; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.FrameProc; + + /// + /// Gets or sets the size (in bytes) of the procedure's frame. + /// + public uint FrameSize + { + get; + set; + } + + /// + /// Gets or sets the size (in bytes) of the procedure's frame padding. + /// + public uint PaddingSize + { + get; + set; + } + + /// + /// Gets or sets the offset relative to the frame pointer where the padding starts. + /// + public int PaddingOffset + { + get; + set; + } + + /// + /// Gets or sets the size (in bytes) of the callee register saves storage. + /// + public uint CalleeSavesSize + { + get; + set; + } + + /// + /// Gets or sets the offset to the exception handler (if available). + /// + public int ExceptionHandlerOffset + { + get; + set; + } + + /// + /// Gets or sets the section index to the exception handler (if available). + /// + public ushort ExceptionHandlerSection + { + get; + set; + } + + /// + /// Gets or sets the attributes associated to the procedure. + /// + public FrameProcedureAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the local base register pointer for the procedure. + /// + public byte LocalBasePointer + { + get => (byte) (((int) Attributes >> 14) & 0b11); + set => Attributes = (Attributes & ~FrameProcedureAttributes.EncodedLocalBasePointerMask) + | (FrameProcedureAttributes) ((value & 0b11) << 14); + } + + /// + /// Gets or sets the parameter base register pointer for the procedure. + /// + public byte ParameterBasePointer + { + get => (byte) (((int) Attributes >> 14) & 0b11); + set => Attributes = (Attributes & ~FrameProcedureAttributes.EncodedParamBasePointerMask) + | (FrameProcedureAttributes) ((value & 0b11) << 16); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/FunctionListSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/FunctionListSymbol.cs new file mode 100644 index 000000000..b86892edf --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/FunctionListSymbol.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Threading; +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides information about a function and the amount of times it is referenced. +/// +/// The function that is referenced. +/// The number of references this function has. +public record struct FunctionCountPair(FunctionIdentifier? Function, int Count); + +/// +/// Represents a symbol containing a list of callers or callees. +/// +public class FunctionListSymbol : CodeViewSymbol +{ + private IList? _entries; + + /// + public override CodeViewSymbolType CodeViewSymbolType => IsCallersList + ? CodeViewSymbolType.Callers + : CodeViewSymbolType.Callees; + + /// + /// Gets or sets a value indicating the functions in this list are callers. + /// + public bool IsCallersList + { + get; + set; + } + + /// + /// Gets or sets a value indicating the functions in this list are callees. + /// + public bool IsCalleesList + { + get => !IsCallersList; + set => IsCallersList = !value; + } + + /// + /// Gets a collection of functions stored in the list. + /// + public IList Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the list of functions. + /// + /// The functions. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ICodeViewSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ICodeViewSymbol.cs new file mode 100644 index 000000000..49484ae98 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ICodeViewSymbol.cs @@ -0,0 +1,15 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a single CodeView symbol record within a PDB file. +/// +public interface ICodeViewSymbol +{ + /// + /// Gets the type of symbol this record encodes. + /// + CodeViewSymbolType CodeViewSymbolType + { + get; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/IRegisterRelativeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/IRegisterRelativeSymbol.cs new file mode 100644 index 000000000..60f4a6cb4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/IRegisterRelativeSymbol.cs @@ -0,0 +1,16 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Describes a variable symbol that is defined by a register and an offset relative to that register. +/// +public interface IRegisterRelativeSymbol : IVariableSymbol +{ + /// + /// Gets or sets the offset relative to the base register. + /// + public int Offset + { + get; + set; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/IScopeCodeViewSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/IScopeCodeViewSymbol.cs new file mode 100644 index 000000000..a4fed22b1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/IScopeCodeViewSymbol.cs @@ -0,0 +1,8 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Describes a symbol that defines one or more sub-symbols. +/// +public interface IScopeCodeViewSymbol : ICodeViewSymbol, ICodeViewSymbolProvider +{ +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/IVariableSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/IVariableSymbol.cs new file mode 100644 index 000000000..5bfe4e4b8 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/IVariableSymbol.cs @@ -0,0 +1,27 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a variable symbol in a PDB file. +/// +public interface IVariableSymbol : ICodeViewSymbol +{ + /// + /// Gets or sets the name of the variable. + /// + public Utf8String? Name + { + get; + set; + } + + /// + /// Gets or sets the value type of the variable. + /// + public CodeViewTypeRecord? VariableType + { + get; + set; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/InlineSiteSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/InlineSiteSymbol.cs new file mode 100644 index 000000000..d4fb13867 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/InlineSiteSymbol.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Threading; +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides information about an inlined call site. +/// +public class InlineSiteSymbol : CodeViewSymbol, IScopeCodeViewSymbol +{ + private readonly LazyVariable _inlinee; + + private IList? _symbols; + private IList? _annotations; + + /// + /// Initializes an empty inline call site symbol. + /// + protected InlineSiteSymbol() + { + _inlinee = new LazyVariable(GetInlinee); + } + + /// + /// Creates a new inline site symbol. + /// + /// The function that is being inlined. + public InlineSiteSymbol(FunctionIdentifier inlinee) + { + _inlinee = new LazyVariable(inlinee); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.InlineSite; + + /// + public IList Symbols + { + get + { + if (_symbols is null) + Interlocked.CompareExchange(ref _symbols, GetSymbols(), null); + return _symbols; + } + } + + /// + /// Gets or sets the identifier of the function that is being inlined. + /// + public FunctionIdentifier? Inlinee + { + get => _inlinee.Value; + set => _inlinee.Value = value; + } + + /// + /// Gets a collection of annotations to the instruction stream. + /// + public IList Annotations + { + get + { + if (_annotations is null) + Interlocked.CompareExchange(ref _annotations, GetAnnotations(), null); + return _annotations; + } + } + + /// + /// Obtains the inlinee. + /// + /// The inlinee + /// + /// This method is called upon initialization of the property. + /// + protected virtual FunctionIdentifier? GetInlinee() => null; + + /// + /// Obtains the sub-symbols defined in this inline site. + /// + /// The symbols. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSymbols() => new List(); + + /// + /// Obtains the collection of annotations added to the instruction stream. + /// + /// The annotations. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetAnnotations() => new List(); + + /// + public override string ToString() => $"S_INLINESITE: {Inlinee} (Annotation Count: {Annotations.Count})"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/LabelSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/LabelSymbol.cs new file mode 100644 index 000000000..ca42dca92 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/LabelSymbol.cs @@ -0,0 +1,83 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a named label within an instruction stream. +/// +public class LabelSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + + /// + /// Initializes an empty label symbol. + /// + protected LabelSymbol() + { + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new label symbol. + /// + /// The name of the label. + /// The index of the segment the label is defined in. + /// The offset within the segment the label is defined at. + /// The attributes describing the label. + public LabelSymbol(Utf8String name, ushort segmentIndex, uint offset, ProcedureAttributes attributes) + { + _name = new LazyVariable(name); + SegmentIndex = segmentIndex; + Offset = offset; + Attributes = attributes; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Label32; + + /// + /// Gets or sets the index of the segment the label is defined in. + /// + public ushort SegmentIndex + { + get; + set; + } + + /// + /// Gets or sets the offset within the segment the label is defined at. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets the attributes describing the label. + /// + public ProcedureAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the name of the label. + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the label. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_LABEL32: [{SegmentIndex:X4}:{Offset:X8}] {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/LocalAddressGap.cs b/src/AsmResolver.Symbols.Pdb/Records/LocalAddressGap.cs new file mode 100644 index 000000000..a6c159db5 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/LocalAddressGap.cs @@ -0,0 +1,44 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Defines a gap within a range of addresses a local variable is valid. +/// +/// The start offset, relative to the beginning of the range. +/// The number of bytes the range spans. +public record struct LocalAddressGap(ushort Start, ushort Length) : IWritable +{ + /// + /// Gets the size in bytes that a local address range gap structure occupies on the disk. + /// + public const uint Size = sizeof(ushort) + sizeof(ushort); + + /// + /// Reads a single local address gap from the provided input stream. + /// + /// The input stream. + /// The range. + public static LocalAddressGap FromReader(ref BinaryStreamReader reader) + { + return new LocalAddressGap( + reader.ReadUInt16(), + reader.ReadUInt16() + ); + } + + /// + /// Gets the (exclusive) end offset of the range. + /// + public ushort End => (ushort) (Start + Length); + + /// + public uint GetPhysicalSize() => Size; + + /// + public void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt16(Start); + writer.WriteUInt16(Length); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/LocalAddressRange.cs b/src/AsmResolver.Symbols.Pdb/Records/LocalAddressRange.cs new file mode 100644 index 000000000..459eb9d8b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/LocalAddressRange.cs @@ -0,0 +1,47 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Defines a range of addresses a local variable is valid. +/// +/// The start address of the range. +/// The section the start address is located in. +/// The number of bytes the range spans. +public record struct LocalAddressRange(uint Start, ushort SectionStart, ushort Length) : IWritable +{ + /// + /// Gets the size in bytes that a local address range structure occupies on the disk. + /// + public const uint EntrySize = sizeof(uint) + sizeof(ushort) + sizeof(ushort); + + /// + /// Reads a single local address range from the provided input stream. + /// + /// The input stream. + /// The range. + public static LocalAddressRange FromReader(ref BinaryStreamReader reader) + { + return new LocalAddressRange( + reader.ReadUInt32(), + reader.ReadUInt16(), + reader.ReadUInt16() + ); + } + + /// + /// Gets the (exclusive) end address of the range. + /// + public uint End => Start + Length; + + /// + public uint GetPhysicalSize() => EntrySize; + + /// + public void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt32(Start); + writer.WriteUInt16(SectionStart); + writer.WriteUInt16(Length); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/LocalAttributes.cs b/src/AsmResolver.Symbols.Pdb/Records/LocalAttributes.cs new file mode 100644 index 000000000..c33e2baeb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/LocalAttributes.cs @@ -0,0 +1,70 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members defining all possible flags that can be assigned to a . +/// +/// +/// This enum is in direct correspondence of the CV_LVARFLAGS enum in cvinfo.h. +/// +[Flags] +public enum LocalAttributes : ushort +{ + /// + /// Indicates the variable is a parameter. + /// + Parameter = 1 << 0, + + /// + /// Indicates the address is taken. + /// + AddrTaken = 1 << 1, + + /// + /// Indicates the variable is compiler generated. + /// + CompilerGenerated = 1 << 2, + + /// + /// Indicates the the symbol is split in temporaries, which are treated by compiler as independent entities. + /// + Aggregate = 1 << 3, + + /// + /// Indicates the Counterpart of and tells that it is a part of an + /// symbol. + /// + Aggregated = 1 << 4, + + /// + /// Indicates the variable has multiple simultaneous lifetimes. + /// + Aliased = 1 << 5, + + /// + /// Indicates the represents one of the multiple simultaneous lifetimes. + /// + Alias = 1 << 6, + + /// + /// Indicates the represents a function return value. + /// + ReturnValue = 1 << 7, + + /// + /// Indicates the variable has no lifetimes. + /// + OptimizedOut = 1 << 8, + + /// + /// Indicates the variable is an enregistered global. + /// + EnregisteredGlobal = 1 << 9, + + /// + /// Indicates the variable is an enregistered static. + /// + EnregisteredStatic = 1 << 10, + +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/LocalSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/LocalSymbol.cs new file mode 100644 index 000000000..2c9cdae46 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/LocalSymbol.cs @@ -0,0 +1,81 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol describing a local variable in a function or method. +/// +public class LocalSymbol : CodeViewSymbol, IVariableSymbol +{ + private readonly LazyVariable _variableType; + private readonly LazyVariable _name; + + /// + /// Initializes an empty local variable symbol. + /// + protected LocalSymbol() + { + _variableType = new LazyVariable(GetVariableType); + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new local variable symbol. + /// + /// The name of the variable. + /// The type of the variable. + /// The attributes describing the variable. + public LocalSymbol(Utf8String? name, CodeViewTypeRecord? variableType, LocalAttributes attributes) + { + _variableType = new LazyVariable(variableType); + _name = new LazyVariable(name); + Attributes = attributes; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Local; + + /// + public CodeViewTypeRecord? VariableType + { + get => _variableType.Value; + set => _variableType.Value = value; + } + + /// + /// Gets or sets the attributes describing the variable. + /// + public LocalAttributes Attributes + { + get; + set; + } + + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the type of the variable. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetVariableType() => null; + + /// + /// Obtains the name of the variable. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_LOCAL32: {VariableType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ObjectNameSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ObjectNameSymbol.cs new file mode 100644 index 000000000..d40a629dd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ObjectNameSymbol.cs @@ -0,0 +1,61 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents an object name symbol in a PDB module. +/// +public class ObjectNameSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + + /// + /// Initializes an empty object name symbol. + /// + protected ObjectNameSymbol() + { + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new object name symbol. + /// + /// The signature of the object. + /// The name of the object. + public ObjectNameSymbol(uint signature, Utf8String name) + { + Signature = signature; + _name = new LazyVariable(name); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.ObjName; + + /// + /// Gets or sets the signature of the object. + /// + public uint Signature + { + get; + set; + } + + /// + /// Gets or sets the name of the object. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the object. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => $"S_OBJNAME: {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ProcedureAttributes.cs b/src/AsmResolver.Symbols.Pdb/Records/ProcedureAttributes.cs new file mode 100644 index 000000000..287d6782f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ProcedureAttributes.cs @@ -0,0 +1,50 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members defining all possible flags that can be used to describe the nature of a procedure symbol. +/// +[Flags] +public enum ProcedureAttributes : byte +{ + /// + /// Indicates the frame pointer is present. + /// + NoFpo = 1 << 0, + + /// + /// Indicates the function uses an interrupt return. + /// + InterruptReturn = 1 << 1, + + /// + /// Indicates the function uses a far return. + /// + FarReturn = 1 << 2, + + /// + /// Indicates the function does not return. + /// + NeverReturn = 1 << 3, + + /// + /// Indicates the function label is not fallen into. + /// + NotReached = 1 << 4, + + /// + /// Indicates the function uses a custom calling convention. + /// + CustomCallingConvention = 1 << 5, + + /// + /// Indicates the function is marked as noinline. + /// + NoInline = 1 << 6, + + /// + /// Indicates the function has debug information for optimized code. + /// + OptimizedDebugInfo = 1 << 7, +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ProcedureSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ProcedureSymbol.cs new file mode 100644 index 000000000..8786de18b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ProcedureSymbol.cs @@ -0,0 +1,218 @@ +using System.Collections.Generic; +using System.Threading; +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents either a local or a global procedure symbol in a PDB file. +/// +public class ProcedureSymbol : CodeViewSymbol, IScopeCodeViewSymbol +{ + private readonly LazyVariable _name; + private readonly LazyVariable _type; + + private IList? _symbols; + + /// + /// Initializes an empty procedure symbol. + /// + protected ProcedureSymbol() + { + _name = new LazyVariable(GetName); + _type = new LazyVariable(GetFunctionType); + } + + /// + /// Creates a new procedure with the provided identifier. + /// + /// The name of the procedure. + /// The function identifier of the procedure. + public ProcedureSymbol(Utf8String name, FunctionIdentifier id) + { + _name = new LazyVariable(name); + _type = new LazyVariable(id); + } + + /// + /// Creates a new procedure with the provided procedure type. + /// + /// The name of the procedure. + /// The type describing the shape of the procedure. + public ProcedureSymbol(Utf8String name, ProcedureTypeRecord type) + { + _name = new LazyVariable(name); + _type = new LazyVariable(type); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType + { + get + { + if (ProcedureType is not null) + { + return IsGlobal + ? CodeViewSymbolType.GProc32 + : CodeViewSymbolType.LProc32; + } + + return IsGlobal + ? CodeViewSymbolType.GProc32Id + : CodeViewSymbolType.LProc32Id; + } + } + + /// + /// Gets a value indicating whether the symbol is a global procedure symbol. + /// + public bool IsGlobal + { + get; + set; + } + + /// + /// Gets a value indicating whether the symbol is a local procedure symbol. + /// + public bool IsLocal + { + get => !IsGlobal; + set => IsGlobal = !value; + } + + /// + public IList Symbols + { + get + { + if (_symbols is null) + Interlocked.CompareExchange(ref _symbols, GetSymbols(), null); + return _symbols; + } + } + + /// + /// Gets or sets the size in bytes of the procedure. + /// + public uint Size + { + get; + set; + } + + /// + /// Gets or sets the start offset where the debug code or data begins of the procedure. + /// + public uint DebugStartOffset + { + get; + set; + } + + /// + /// Gets or sets the end offset where the debug code or data begins of the procedure. + /// + public uint DebugEndOffset + { + get; + set; + } + + /// + /// Gets or sets the type or identifier leaf describing the identifier or shape of the function. + /// + public CodeViewLeaf? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the function identifier of the procedure (if available). + /// + public FunctionIdentifier? FunctionId + { + get => Type as FunctionIdentifier; + set => Type = value; + } + + /// + /// Gets or sets the type describing the shape of the procedure (if available). + /// + public ProcedureTypeRecord? ProcedureType + { + get => Type as ProcedureTypeRecord; + set => Type = value; + } + + /// + /// Gets or sets the segment index in which the procedure is defined in. + /// + public ushort SegmentIndex + { + get; + set; + } + + /// + /// Gets or sets the offset within the segment in which the procedure starts at. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets attributes describing the nature of the procedure. + /// + public ProcedureAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the name of the procedure. + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the sub-symbols defined in this procedure. + /// + /// The symbols. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSymbols() => new List(); + + /// + /// Obtains the function type of this procedure. + /// + /// The function type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewLeaf? GetFunctionType() => null; + + /// + /// Obtains the name of this procedure. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() + { + string prefix = CodeViewSymbolType.ToString().ToUpper(); + return $"S_{prefix}: [{SegmentIndex:X4}:{Offset:X8}] {Name} ({Type})"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs index 08515a42b..8d89e4212 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs @@ -18,13 +18,13 @@ protected PublicSymbol() /// /// Creates a new public symbol. /// - /// The segment index. + /// 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) + public PublicSymbol(ushort segmentIndex, uint offset, Utf8String name, PublicSymbolAttributes attributes) { - Segment = segment; + SegmentIndex = segmentIndex; Offset = offset; _name = new LazyVariable(name); Attributes = attributes; @@ -36,7 +36,7 @@ public PublicSymbol(ushort segment, uint offset, Utf8String name, PublicSymbolAt /// /// Gets or sets the file segment index this symbol is located in. /// - public ushort Segment + public ushort SegmentIndex { get; set; @@ -119,5 +119,5 @@ public Utf8String Name protected virtual Utf8String GetName() => Utf8String.Empty; /// - public override string ToString() => $"{CodeViewSymbolType}: [{Segment:X4}:{Offset:X8}] {Name}"; + public override string ToString() => $"S_PUB32: [{SegmentIndex:X4}:{Offset:X8}] {Name}"; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/RegisterRangeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/RegisterRangeSymbol.cs new file mode 100644 index 000000000..e2219666e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/RegisterRangeSymbol.cs @@ -0,0 +1,50 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Defines an address range in which a local variable or parameter is defined that is fully defined by a register. +/// +public class RegisterRangeSymbol : DefinitionRangeSymbol +{ + /// + /// Initializes an empty register range symbol. + /// + protected RegisterRangeSymbol() + { + } + + /// + /// Creates a new register range symbol. + /// + /// The register. + /// The range in which the symbol is valid. + public RegisterRangeSymbol(ushort register, LocalAddressRange range) + { + Register = register; + Range = range; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.DefRangeRegister; + + /// + /// Gets or sets the register that defines the symbol. + /// + public ushort Register + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether the symbol may have no user name on one of the control flow paths + /// within the function. + /// + public bool IsMaybe + { + get; + set; + } + + /// + public override string ToString() => $"S_DEFRANGE_REGISTER: {Range} (Gap Count: {Gaps.Count})"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeRangeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeRangeSymbol.cs new file mode 100644 index 000000000..a3981d632 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeRangeSymbol.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Defines an address range in which a local variable or parameter is defined that is fully defined by a register and +/// a relative starting offset and length. +/// +public class RegisterRelativeRangeSymbol : DefinitionRangeSymbol +{ + /// + /// Initializes an empty relative register range. + /// + protected RegisterRelativeRangeSymbol() + { + } + + /// + /// Creates a new range. + /// + /// The base register. + /// The offset. + /// The address range this range is valid. + /// A collection of gaps within the range that the symbol is invalid. + public RegisterRelativeRangeSymbol(ushort baseRegister, int offset, LocalAddressRange range, IEnumerable gaps) + : base(range, gaps) + { + BaseRegister = baseRegister; + Offset = offset; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.DefRangeRegisterRel; + + /// + /// Gets or sets the base register holding the pointer of the symbol. + /// + public ushort BaseRegister + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether the symbol is spilled. + /// + public bool IsSpilledUdtMember + { + get; + set; + } + + /// + /// Gets or sets the offset within the parent variable this symbol starts at. + /// + public int ParentOffset + { + get; + set; + } + + /// + /// Gets or sets the offset from the base register this symbol is defined at. + /// + public int Offset + { + get; + set; + } + + /// + public override string ToString() => $"S_DEFRANGE_REGISTER_REL: {Range} (Gap Count: {Gaps.Count})"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeSymbol.cs new file mode 100644 index 000000000..4871ea858 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeSymbol.cs @@ -0,0 +1,92 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol that is defined by a register+offset pair. +/// +public class RegisterRelativeSymbol : CodeViewSymbol, IRegisterRelativeSymbol +{ + private readonly LazyVariable _variableType; + private readonly LazyVariable _name; + + /// + /// Initializes an empty relative register symbol. + /// + protected RegisterRelativeSymbol() + { + _variableType = new LazyVariable(GetVariableType); + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new relative register symbol. + /// + /// The name of the symbol. + /// The base register. + /// The offset to the register. + /// The type of variable the register+offset pair stores. + public RegisterRelativeSymbol(Utf8String name, ushort baseRegister, int offset, CodeViewTypeRecord variableType) + { + _name = new LazyVariable(name); + BaseRegister = baseRegister; + Offset = offset; + _variableType = new LazyVariable(variableType); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.RegRel32; + + /// + /// Gets or sets the base register. + /// + public ushort BaseRegister + { + get; + set; + } + + /// + /// Gets or sets the offset relative to the base register. + /// + public int Offset + { + get; + set; + } + + /// + public CodeViewTypeRecord? VariableType + { + get => _variableType.Value; + set => _variableType.Value = value; + } + + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the variable type of the symbol. + /// + /// The variable type + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetVariableType() => null; + + /// + /// Obtains the name of the symbol. + /// + /// The name + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_REGREL32: [+{Offset:X}] {VariableType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/RegisterSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/RegisterSymbol.cs new file mode 100644 index 000000000..9cf7bde0b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/RegisterSymbol.cs @@ -0,0 +1,81 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol describing a register variable in a function or method. +/// +public class RegisterSymbol : CodeViewSymbol, IVariableSymbol +{ + private readonly LazyVariable _variableType; + private readonly LazyVariable _name; + + /// + /// Initializes an empty register variable symbol. + /// + protected RegisterSymbol() + { + _variableType = new LazyVariable(GetVariableType); + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new register variable symbol. + /// + /// The name of the variable. + /// The type of the variable. + /// The register that defines the variable. + public RegisterSymbol(Utf8String? name, CodeViewTypeRecord? variableType, ushort register) + { + Register = register; + _variableType = new LazyVariable(variableType); + _name = new LazyVariable(name); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Register; + + /// + /// Gets or sets the register that defines the variable. + /// + public ushort Register + { + get; + set; + } + + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + public CodeViewTypeRecord? VariableType + { + get => _variableType.Value; + set => _variableType.Value = value; + } + + /// + /// Obtains the type of the variable. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetVariableType() => null; + + /// + /// Obtains the name of the variable. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_REGISTER: {VariableType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/SectionSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/SectionSymbol.cs new file mode 100644 index 000000000..a6885e204 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/SectionSymbol.cs @@ -0,0 +1,100 @@ +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides information about a section within a PE file. +/// +public class SectionSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + + /// + /// Initializes an empty section symbol. + /// + protected SectionSymbol() + { + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new section symbol. + /// + /// The name of the section. + public SectionSymbol(Utf8String name) + { + _name = new LazyVariable(name); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Section; + + /// + /// Gets or sets the section number within the PE file. + /// + public ushort SectionNumber + { + get; + set; + } + + /// + /// Gets or sets the alignment of the section. + /// + /// + /// This should be a power of 2. + /// + public uint Alignment + { + get; + set; + } + + /// + /// Gets or sets the starting relative virtual address (RVA) of the section. + /// + public uint Rva + { + get; + set; + } + + /// + /// Gets or sets the size in bytes of the section. + /// + public uint Size + { + get; + set; + } + + /// + /// Gets or sets the section flags describing the nature of the section. + /// + public SectionFlags Attributes + { + get; + set; + } + + /// + /// Gets or sets the name of the section. + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the section. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_SECTION: [{SectionNumber:X4}] {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedBasePointerRelativeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedBasePointerRelativeSymbol.cs new file mode 100644 index 000000000..2e368f535 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedBasePointerRelativeSymbol.cs @@ -0,0 +1,42 @@ +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 SerializedBasePointerRelativeSymbol : BasePointerRelativeSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a base-pointer relative symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedBasePointerRelativeSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + + Offset = reader.ReadInt32(); + _typeIndex = reader.ReadUInt32(); + + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetVariableType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Base-pointer relative symbol contains an invalid type index {_typeIndex:X8}."); + } + +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedBuildInfoSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedBuildInfoSymbol.cs new file mode 100644 index 000000000..098c9de37 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedBuildInfoSymbol.cs @@ -0,0 +1,33 @@ +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 SerializedBuildInfoSymbol : BuildInfoSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _idIndex; + + /// + /// Reads a build information symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedBuildInfoSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _idIndex = reader.ReadUInt32(); + } + + /// + protected override BuildInfoLeaf? GetInfo() + { + return _context.ParentImage.TryGetIdLeafRecord(_idIndex, out BuildInfoLeaf? leaf) + ? leaf + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Build information symbol contains an invalid ID index {_idIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCallSiteSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCallSiteSymbol.cs new file mode 100644 index 000000000..a63fb747c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCallSiteSymbol.cs @@ -0,0 +1,36 @@ +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 SerializedCallSiteSymbol : CallSiteSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + + /// + /// Reads a call site symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedCallSiteSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + Offset = reader.ReadInt32(); + SectionIndex = reader.ReadUInt16(); + _ = reader.ReadUInt16(); // padding + _typeIndex = reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetFunctionType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Call site symbol contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCoffGroup.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCoffGroup.cs new file mode 100644 index 000000000..f8ac85704 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCoffGroup.cs @@ -0,0 +1,29 @@ +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedCoffGroup : CoffGroupSymbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a COFF group symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedCoffGroup(BinaryStreamReader reader) + { + Size = reader.ReadUInt32(); + Characteristics = (SectionFlags) reader.ReadUInt32(); + Offset = reader.ReadUInt32(); + SegmentIndex = reader.ReadUInt16(); + + _nameReader = reader; + } + + /// + protected override Utf8String? GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCompile2Symbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCompile2Symbol.cs new file mode 100644 index 000000000..5655aee62 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCompile2Symbol.cs @@ -0,0 +1,33 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedCompile2Symbol : Compile2Symbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a compile symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedCompile2Symbol(BinaryStreamReader reader) + { + uint flags = reader.ReadUInt32(); + Language = (SourceLanguage) (flags & 0xF); + Attributes = (CompileAttributes) (flags >> 8); + Machine = (CpuType) reader.ReadUInt16(); + FrontEndMajorVersion = reader.ReadUInt16(); + FrontEndMinorVersion = reader.ReadUInt16(); + FrontEndBuildVersion = reader.ReadUInt16(); + BackEndMajorVersion = reader.ReadUInt16(); + BackEndMinorVersion = reader.ReadUInt16(); + BackEndBuildVersion = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetCompilerVersion() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCompile3Symbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCompile3Symbol.cs new file mode 100644 index 000000000..3a3076cf1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedCompile3Symbol.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedCompile3Symbol : Compile3Symbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a compile symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedCompile3Symbol(BinaryStreamReader reader) + { + uint flags = reader.ReadUInt32(); + Language = (SourceLanguage) (flags & 0xF); + Attributes = (CompileAttributes) (flags >> 8); + Machine = (CpuType) reader.ReadUInt16(); + FrontEndMajorVersion = reader.ReadUInt16(); + FrontEndMinorVersion = reader.ReadUInt16(); + FrontEndBuildVersion = reader.ReadUInt16(); + FrontEndQfeVersion = reader.ReadUInt16(); + BackEndMajorVersion = reader.ReadUInt16(); + BackEndMinorVersion = reader.ReadUInt16(); + BackEndBuildVersion = reader.ReadUInt16(); + BackEndQfeVersion = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetCompilerVersion() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs index 3c3c31c53..3756e47ca 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs @@ -31,7 +31,7 @@ public SerializedConstantSymbol(PdbReaderContext context, BinaryStreamReader rea /// protected override CodeViewTypeRecord? GetConstantType() { - return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) ? type : _context.Parameters.ErrorListener.BadImageAndReturn( $"Constant contains an invalid type index {_typeIndex:X8}."); diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedDataSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedDataSymbol.cs new file mode 100644 index 000000000..05cdd0d8b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedDataSymbol.cs @@ -0,0 +1,44 @@ +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 SerializedDataSymbol : DataSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a data symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + /// Indicates the symbol is a global data symbol. + public SerializedDataSymbol(PdbReaderContext context, BinaryStreamReader reader, bool isGlobal) + { + _context = context; + + _typeIndex = reader.ReadUInt32(); + Offset = reader.ReadUInt32(); + SegmentIndex = reader.ReadUInt16(); + IsGlobal = isGlobal; + + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetVariableType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Data symbol contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedEnvironmentBlockSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedEnvironmentBlockSymbol.cs new file mode 100644 index 000000000..d5416d7e2 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedEnvironmentBlockSymbol.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedEnvironmentBlockSymbol : EnvironmentBlockSymbol +{ + private readonly BinaryStreamReader _entriesReader; + + /// + /// Reads an environment block symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedEnvironmentBlockSymbol(BinaryStreamReader reader) + { + reader.ReadByte(); // padding? + _entriesReader = reader; + } + + /// + protected override IList> GetEntries() + { + var result = new List>(); + + var reader = _entriesReader.Fork(); + while (reader.CanRead(sizeof(byte)) && reader.PeekByte() != 0) + { + var key = reader.ReadUtf8String(); + var value = reader.ReadUtf8String(); + result.Add(new KeyValuePair(key, value)); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFileStaticSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFileStaticSymbol.cs new file mode 100644 index 000000000..b8237affd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFileStaticSymbol.cs @@ -0,0 +1,42 @@ +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 SerializedFileStaticSymbol : FileStaticSymbol +{ + private readonly uint _typeIndex; + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a file static symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedFileStaticSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + + _typeIndex = reader.ReadUInt32(); + ModuleFileNameOffset = reader.ReadUInt32(); + Attributes = (LocalAttributes) reader.ReadUInt16(); + + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetVariableType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"File static symbol contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFrameCookieSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFrameCookieSymbol.cs new file mode 100644 index 000000000..92d99d5b3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFrameCookieSymbol.cs @@ -0,0 +1,21 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedFrameCookieSymbol : FrameCookieSymbol +{ + /// + /// Reads a frame cookie symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedFrameCookieSymbol(BinaryStreamReader reader) + { + FrameOffset = reader.ReadInt32(); + Register = reader.ReadUInt16(); + CookieType = (FrameCookieType) reader.ReadByte(); + Attributes = reader.ReadByte(); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFramePointerRangeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFramePointerRangeSymbol.cs new file mode 100644 index 000000000..d96a24d83 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFramePointerRangeSymbol.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedFramePointerRangeSymbol : FramePointerRangeSymbol +{ + private readonly BinaryStreamReader? _gapsReader; + + /// + /// Reads a frame-pointer def-range from the provided input stream. + /// + /// The input stream to read from. + /// Indicates whether the range is implicitly defined by the containing function scope. + public SerializedFramePointerRangeSymbol(BinaryStreamReader reader, bool isFullScope) + { + Offset = reader.ReadInt32(); + IsFullScope = isFullScope; + + if (!isFullScope) + { + Range = LocalAddressRange.FromReader(ref reader); + _gapsReader = reader; + } + } + + /// + protected override IList GetGaps() + { + var result = new List(); + if (_gapsReader is null) + return result; + + var reader = _gapsReader.Value.Fork(); + while (reader.CanRead(LocalAddressGap.Size)) + result.Add(LocalAddressGap.FromReader(ref reader)); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFrameProcedureSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFrameProcedureSymbol.cs new file mode 100644 index 000000000..474c36a26 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFrameProcedureSymbol.cs @@ -0,0 +1,24 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedFrameProcedureSymbol : FrameProcedureSymbol +{ + /// + /// Reads a frame proceudre symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedFrameProcedureSymbol(BinaryStreamReader reader) + { + FrameSize = reader.ReadUInt32(); + PaddingSize = reader.ReadUInt32(); + PaddingOffset = reader.ReadInt32(); + CalleeSavesSize = reader.ReadUInt32(); + ExceptionHandlerOffset = reader.ReadInt32(); + ExceptionHandlerSection = reader.ReadUInt16(); + Attributes = (FrameProcedureAttributes) reader.ReadUInt32(); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFunctionListSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFunctionListSymbol.cs new file mode 100644 index 000000000..3f9100e31 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedFunctionListSymbol.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +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 SerializedFunctionListSymbol : FunctionListSymbol +{ + private readonly int _count; + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _entriesReader; + + /// + /// Reads a function list symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + /// Indicates the functions in the list are callers of the procedure. + public SerializedFunctionListSymbol(PdbReaderContext context, BinaryStreamReader reader, bool isCallers) + { + IsCallersList = isCallers; + _context = context; + _count = reader.ReadInt32(); + + _entriesReader = reader; + } + + /// + protected override IList GetEntries() + { + var result = new List(_count); + + var indexReader = _entriesReader.Fork(); + var countReader = _entriesReader.ForkRelative((uint) (_count + 1) * sizeof(int)); + + for (int i = 0; i < _count; i++) + { + uint typeIndex = indexReader.ReadUInt32(); + int count = countReader.CanRead(sizeof(int)) + ? countReader.ReadInt32() + : 0; + + var function = _context.ParentImage.TryGetIdLeafRecord(typeIndex, out FunctionIdentifier? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Function list contains an invalid type index {typeIndex:X8}."); + + result.Add(new FunctionCountPair(function, count)); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedInlineSiteSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedInlineSiteSymbol.cs new file mode 100644 index 000000000..5715df33a --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedInlineSiteSymbol.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +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 SerializedInlineSiteSymbol : InlineSiteSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _inlineeIndex; + + private readonly BinaryStreamReader _annotationsReader; + + /// + /// Reads a function list symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedInlineSiteSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _ = reader.ReadUInt32(); // pParent. + _ = reader.ReadUInt32(); // pEnd. + _inlineeIndex = reader.ReadUInt32(); + + _annotationsReader = reader; + } + + /// + protected override FunctionIdentifier? GetInlinee() + { + return _context.ParentImage.TryGetIdLeafRecord(_inlineeIndex, out FunctionIdentifier? id) + ? id + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Inline Site symbol contains an invalid type index {_inlineeIndex:X8}."); + } + + /// + protected override IList GetAnnotations() + { + var result = new List(); + + var reader = _annotationsReader.Fork(); + while (reader.TryReadCompressedUInt32(out uint raw)) + { + var opCode = (BinaryAnnotationOpCode) raw; + + // All opcodes have at least one operand. + if (!reader.TryReadCompressedUInt32(out uint operand1)) + { + _context.Parameters.ErrorListener.BadImage( + $"Expected first operand of binary annotation at offset {reader.Offset:X8}."); + break; + } + + // Check if we need to read a second operand. + uint operand2 = 0; + if (opCode == BinaryAnnotationOpCode.ChangeCodeLengthAndCodeOffset + && !reader.TryReadCompressedUInt32(out operand2)) + { + _context.Parameters.ErrorListener.BadImage( + $"Expected second operand of binary annotation at offset {reader.Offset:X8}."); + break; + } + + // Add to result. + result.Add(new BinaryAnnotation(opCode, operand1, operand2)); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedLabelSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedLabelSymbol.cs new file mode 100644 index 000000000..a5c2ad3e4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedLabelSymbol.cs @@ -0,0 +1,28 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedLabelSymbol : LabelSymbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a label symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedLabelSymbol(BinaryStreamReader reader) + { + Offset = reader.ReadUInt32(); + SegmentIndex = reader.ReadUInt16(); + Attributes = (ProcedureAttributes) reader.ReadByte(); + + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedLocalSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedLocalSymbol.cs new file mode 100644 index 000000000..43ffd1fb9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedLocalSymbol.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 SerializedLocalSymbol : LocalSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a local variable symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedLocalSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _typeIndex = reader.ReadUInt32(); + Attributes = (LocalAttributes) reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetVariableType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Local symbol contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedObjectNameSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedObjectNameSymbol.cs new file mode 100644 index 000000000..f4cd73ca2 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedObjectNameSymbol.cs @@ -0,0 +1,24 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedObjectNameSymbol : ObjectNameSymbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an object name from the provided input stream. + /// + /// The input stream to read from. + public SerializedObjectNameSymbol(BinaryStreamReader reader) + { + Signature = reader.ReadUInt32(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedProcedureSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedProcedureSymbol.cs new file mode 100644 index 000000000..92357498b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedProcedureSymbol.cs @@ -0,0 +1,69 @@ +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 SerializedProcedureSymbol : ProcedureSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + private readonly bool _isId; + + /// + /// Reads a procedure symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + /// Indicates the symbol is a global symbol. + /// Indicates the symbol references a function ID instead of a procedure type. + public SerializedProcedureSymbol(PdbReaderContext context, BinaryStreamReader reader, bool isGlobal, bool isId) + { + _context = context; + + _ = reader.ReadUInt32(); // pParent + _ = reader.ReadUInt32(); // pEnd + _ = reader.ReadUInt32(); // pNext + + Size = reader.ReadUInt32(); + DebugStartOffset = reader.ReadUInt32(); + DebugEndOffset = reader.ReadUInt32(); + + _typeIndex = reader.ReadUInt32(); + + Offset = reader.ReadUInt32(); + SegmentIndex = reader.ReadUInt16(); + Attributes = (ProcedureAttributes) reader.ReadByte(); + + _nameReader = reader; + IsGlobal = isGlobal; + _isId = isId; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewLeaf? GetFunctionType() + { + if (_typeIndex == 0) + return null; + + if (_isId) + { + return !_context.ParentImage.TryGetIdLeafRecord(_typeIndex, out FunctionIdentifier? id) + ? _context.Parameters.ErrorListener.BadImageAndReturn( + $"Procedure symbol contains an invalid ID index {_typeIndex:X8}.") + : id; + } + + return !_context.ParentImage.TryGetLeafRecord(_typeIndex, out ProcedureTypeRecord? procedure) + ? _context.Parameters.ErrorListener.BadImageAndReturn( + $"Procedure symbol contains an invalid type index {_typeIndex:X8}.") + : procedure; + } + +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs index f1a815036..1581f9817 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs @@ -17,7 +17,7 @@ public SerializedPublicSymbol(BinaryStreamReader reader) { Attributes = (PublicSymbolAttributes) reader.ReadUInt32(); Offset = reader.ReadUInt32(); - Segment = reader.ReadUInt16(); + SegmentIndex = reader.ReadUInt16(); _nameReader = reader; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRangeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRangeSymbol.cs new file mode 100644 index 000000000..85ba56edb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRangeSymbol.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedRegisterRangeSymbol : RegisterRangeSymbol +{ + private readonly BinaryStreamReader _gapsReader; + + /// + /// Reads a register def-range symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedRegisterRangeSymbol(BinaryStreamReader reader) + { + Register = reader.ReadUInt16(); + + ushort flags = reader.ReadUInt16(); + IsMaybe = (flags & 1) != 0; + + Range = LocalAddressRange.FromReader(ref reader); + + _gapsReader = reader; + } + + /// + protected override IList GetGaps() + { + var result = new List(); + + var reader = _gapsReader.Fork(); + while (reader.CanRead(LocalAddressGap.Size)) + result.Add(LocalAddressGap.FromReader(ref reader)); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRelativeRangeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRelativeRangeSymbol.cs new file mode 100644 index 000000000..67dd97ae5 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRelativeRangeSymbol.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedRegisterRelativeRangeSymbol : RegisterRelativeRangeSymbol +{ + private readonly BinaryStreamReader _gapsReader; + + /// + /// Reads a relative register def-range symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedRegisterRelativeRangeSymbol(BinaryStreamReader reader) + { + BaseRegister = reader.ReadUInt16(); + + ushort flags = reader.ReadUInt16(); + IsSpilledUdtMember = (flags & 1) != 0; + ParentOffset = (flags >> 4) & 0b0000_1111_1111_1111; + Offset = reader.ReadInt32(); + + Range = LocalAddressRange.FromReader(ref reader); + + _gapsReader = reader; + } + + /// + protected override IList GetGaps() + { + var result = new List(); + + var reader = _gapsReader.Fork(); + while (reader.CanRead(LocalAddressGap.Size)) + result.Add(LocalAddressGap.FromReader(ref reader)); + + return result; + } + +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRelativeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRelativeSymbol.cs new file mode 100644 index 000000000..5e961302f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterRelativeSymbol.cs @@ -0,0 +1,42 @@ +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 SerializedRegisterRelativeSymbol : RegisterRelativeSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a register+offset pair symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedRegisterRelativeSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + + Offset = reader.ReadInt32(); + _typeIndex = reader.ReadUInt32(); + BaseRegister = reader.ReadUInt16(); + + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetVariableType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Relative register symbol contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterSymbol.cs new file mode 100644 index 000000000..fa7e4fcee --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedRegisterSymbol.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 SerializedRegisterSymbol : RegisterSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a register variable symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedRegisterSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _typeIndex = reader.ReadUInt32(); + Register = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetVariableType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out CodeViewTypeRecord? type) + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Register symbol contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedSectionSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedSectionSymbol.cs new file mode 100644 index 000000000..759aaca25 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedSectionSymbol.cs @@ -0,0 +1,31 @@ +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedSectionSymbol : SectionSymbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a section symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedSectionSymbol(BinaryStreamReader reader) + { + SectionNumber = reader.ReadUInt16(); + Alignment = (uint) (1 << reader.ReadByte()); + reader.ReadByte(); // reserved + Rva = reader.ReadUInt32(); + Size = reader.ReadUInt32(); + Attributes = (SectionFlags) reader.ReadUInt32(); + + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedThunkSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedThunkSymbol.cs new file mode 100644 index 000000000..f533d1263 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedThunkSymbol.cs @@ -0,0 +1,37 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedThunkSymbol : ThunkSymbol +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a thunk symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedThunkSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + + _ = reader.ReadUInt32(); // pParent + _ = reader.ReadUInt32(); // pEnd + _ = reader.ReadUInt32(); // pNext + + Offset = reader.ReadUInt32(); + SegmentIndex = reader.ReadUInt16(); + Size = reader.ReadUInt16(); + Ordinal = (ThunkOrdinal) reader.ReadByte(); + + _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 index fdcce9a7f..cce3af18f 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs @@ -30,7 +30,7 @@ public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamRea /// protected override CodeViewTypeRecord? GetSymbolType() { - return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out 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/Serialized/SerializedUsingNamespaceSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUsingNamespaceSymbol.cs new file mode 100644 index 000000000..852234a7b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUsingNamespaceSymbol.cs @@ -0,0 +1,23 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedUsingNamespaceSymbol : UsingNamespaceSymbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a user-defined type symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedUsingNamespaceSymbol(BinaryStreamReader reader) + { + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SymbolStreamReader.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SymbolStreamReader.cs new file mode 100644 index 000000000..d197bcc78 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SymbolStreamReader.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +internal static class SymbolStreamReader +{ + public static IList ReadSymbols( + PdbReaderContext context, + ref BinaryStreamReader reader) + { + var result = new List(); + var scopeStack = new Stack(); + + while (reader.CanRead(sizeof(ushort) * 2)) + { + if (scopeStack.Count > 0) + { + var lookahead = reader.Fork(); + ushort length = lookahead.ReadUInt16(); + var symbolType = (CodeViewSymbolType) lookahead.ReadUInt16(); + + // If we are at an S_END or S_INLINESITEEND symbol, we reached the end of a scope. + // Silently consume the end symbol and go up the scope stack. + if (symbolType is CodeViewSymbolType.End or CodeViewSymbolType.InlineSiteEnd) + { + reader.Offset += sizeof(ushort) + (uint) length; + scopeStack.Pop(); + continue; + } + } + + // Read the next symbol. + var nextSymbol = CodeViewSymbol.FromReader(context, ref reader); + + // If we're in a scope, add it to the scope, otherwise add it to the end result. + if (scopeStack.Count > 0) + scopeStack.Peek().Symbols.Add(nextSymbol); + else + result.Add(nextSymbol); + + // Are we entering a new scope with this next symbol? + if (nextSymbol is IScopeCodeViewSymbol scopeSymbol) + scopeStack.Push(scopeSymbol); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/SourceLanguage.cs b/src/AsmResolver.Symbols.Pdb/Records/SourceLanguage.cs new file mode 100644 index 000000000..99523c9ae --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/SourceLanguage.cs @@ -0,0 +1,32 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members describing all languages that the PDB file format supports. +/// +public enum SourceLanguage : byte +{ +#pragma warning disable CS1591 + C = 0x00, + Cpp = 0x01, + Fortran = 0x02, + Masm = 0x03, + Pascal = 0x04, + Basic = 0x05, + Cobol = 0x06, + Link = 0x07, + Cvtres = 0x08, + Cvtpgd = 0x09, + CSharp = 0x0a, + VB = 0x0b, + ILAsm = 0x0c, + Java = 0x0d, + JScript = 0x0e, + MSIL = 0x0f, + HLSL = 0x10, + + Rust = 0x15, + + D = (byte)'D', + Swift = (byte)'S', +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ThunkOrdinal.cs b/src/AsmResolver.Symbols.Pdb/Records/ThunkOrdinal.cs new file mode 100644 index 000000000..dff8bc28e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ThunkOrdinal.cs @@ -0,0 +1,17 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Describes the type of data that is stored within a thunk symbol. +/// +public enum ThunkOrdinal : byte +{ +#pragma warning disable CS1591 + NoType, + Adjustor, + VCall, + PCode, + Load, + IncrementalTrampoline, + BranchIslandTrampoline +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ThunkSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ThunkSymbol.cs new file mode 100644 index 000000000..e765b660e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ThunkSymbol.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a thunk symbol in a PDB module. +/// +public class ThunkSymbol : CodeViewSymbol, IScopeCodeViewSymbol +{ + private readonly LazyVariable _name; + + private IList? _symbols; + + /// + /// Initializes an empty thunk symbol. + /// + protected ThunkSymbol() + { + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new named thunk symbol. + /// + /// The name. + /// The size of the thunk in bytes. + public ThunkSymbol(Utf8String name, ushort size) + { + _name = new LazyVariable(name); + Size = size; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Thunk32; + + /// + public IList Symbols + { + get + { + if (_symbols is null) + Interlocked.CompareExchange(ref _symbols, GetSymbols(), null); + return _symbols; + } + } + + /// + /// Gets or sets the index of the segment the thunk is defined in. + /// + public ushort SegmentIndex + { + get; + set; + } + + /// + /// Gets or sets the offset within the segment the thunk is defined in. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets the size of the thunk in bytes. + /// + public ushort Size + { + get; + set; + } + + /// + /// Gets or sets the ordinal of the thunk. + /// + public ThunkOrdinal Ordinal + { + get; + set; + } + + /// + /// Gets or sets the name of the thunk. + /// + public Utf8String? Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the symbols defined within the thunk. + /// + /// The symbols. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSymbols() => new List(); + + /// + /// Obtains the name of the thunk. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => null; + + /// + public override string ToString() => $"S_THUNK32: [{SegmentIndex:X4}:{Offset:X8}] {Name} ({Ordinal})"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs index 983173876..29790f7ba 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs @@ -31,5 +31,5 @@ public byte[] Data } /// - public override string ToString() => $"{CodeViewSymbolType.ToString()} ({Data.Length.ToString()} bytes)"; + public override string ToString() => $"S_{CodeViewSymbolType.ToString().ToUpper()} ({Data.Length.ToString()} bytes)"; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs index 4e84a3aa3..d7f4e8e10 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -70,5 +70,5 @@ public CodeViewTypeRecord Type protected virtual CodeViewTypeRecord? GetSymbolType() => null; /// - public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name}"; + public override string ToString() => $"S_UDT: {Type} {Name}"; } diff --git a/src/AsmResolver.Symbols.Pdb/Records/UsingNamespaceSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UsingNamespaceSymbol.cs new file mode 100644 index 000000000..05badd80c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/UsingNamespaceSymbol.cs @@ -0,0 +1,50 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a using directive that allows for types to be used without specifying its declaring namespace. +/// +public class UsingNamespaceSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + + /// + /// Initializes a new empty using namespace. + /// + protected UsingNamespaceSymbol() + { + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new using namespace record. + /// + /// The namespace to use. + public UsingNamespaceSymbol(Utf8String name) + { + _name = new LazyVariable(name); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.UNamespace; + + /// + /// Gets or sets the name of the namespace that is being used. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the namespace. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetName() => Utf8String.Empty; + + /// + public override string ToString() => $"S_UNAMESPACE: {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs index 32bc7dcd6..5100d516f 100644 --- a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -1,14 +1,16 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; 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.Modi; using AsmResolver.Symbols.Pdb.Metadata.Tpi; using AsmResolver.Symbols.Pdb.Msf; using AsmResolver.Symbols.Pdb.Records; +using AsmResolver.Symbols.Pdb.Records.Serialized; namespace AsmResolver.Symbols.Pdb; @@ -19,7 +21,8 @@ public class SerializedPdbImage : PdbImage { private const int MinimalRequiredStreamCount = 5; private readonly MsfFile _file; - private CodeViewLeaf?[]? _leaves; + private readonly LeafStreamCache _tpi; + private readonly LeafStreamCache _ipi; /// /// Interprets a PDB image from the provided MSF file. @@ -30,14 +33,31 @@ public SerializedPdbImage(MsfFile file, PdbReaderParameters readerParameters) { _file = file; + // Obtain relevant core PDB streams. 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()); + TpiStream = TpiStream.FromReader(file.Streams[TpiStream.TpiStreamIndex].CreateReader()); + IpiStream = TpiStream.FromReader(file.Streams[TpiStream.IpiStreamIndex].CreateReader()); + // Initialize TPI/IPI caches. ReaderContext = new PdbReaderContext(this, readerParameters); + _tpi = new LeafStreamCache(ReaderContext, TpiStream); + _ipi = new LeafStreamCache(ReaderContext, IpiStream); + + // Copy over INFO stream metadata. + Timestamp = new DateTime(1970, 1, 1) + TimeSpan.FromSeconds(InfoStream.Signature); + Age = InfoStream.Age; + UniqueId = InfoStream.UniqueId; + + // Copy over DBI stream metadata. + BuildMajorVersion = DbiStream.BuildMajorVersion; + BuildMinorVersion = DbiStream.BuildMinorVersion; + PdbDllVersion = DbiStream.PdbDllVersion; + Attributes = DbiStream.Attributes; + Machine = DbiStream.Machine; } internal PdbReaderContext ReaderContext @@ -60,54 +80,138 @@ internal TpiStream TpiStream get; } - [MemberNotNull(nameof(_leaves))] - private void EnsureLeavesInitialized() + internal TpiStream IpiStream { - if (_leaves is null) - { - Interlocked.CompareExchange(ref _leaves, - new CodeViewLeaf?[TpiStream.TypeIndexEnd - TpiStream.TypeIndexBegin], null); - } + get; } /// - public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf) - { - if (typeIndex < TpiStream.TypeIndexBegin) - return base.TryGetLeafRecord(typeIndex, out leaf); + public override IEnumerable GetLeafRecords() => _tpi.GetRecords().Cast(); - EnsureLeavesInitialized(); + /// + public override IEnumerable GetIdLeafRecords() => _ipi.GetRecords().Cast(); - if (typeIndex >= TpiStream.TypeIndexBegin && typeIndex < TpiStream.TypeIndexEnd) + /// + public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out ITpiLeaf? leaf) + { + if (_tpi.TryGetRecord(typeIndex, out var x) && x is ITpiLeaf y) { - 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 = y; + return true; } - leaf = null; - return false; + return base.TryGetLeafRecord(typeIndex, out leaf); } /// - protected override IList GetSymbols() + public override bool TryGetIdLeafRecord(uint idIndex, [NotNullWhen(true)] out IIpiLeaf? leaf) { - var result = new List(); + if (_ipi.TryGetRecord(idIndex, out var x) && x is IIpiLeaf y) + { + leaf = y; + return true; + } + + return base.TryGetIdLeafRecord(idIndex, out leaf); + } + /// + protected override IList GetSymbols() + { int index = DbiStream.SymbolRecordStreamIndex; if (index >= _file.Streams.Count) - return result; + return new List(); var reader = _file.Streams[DbiStream.SymbolRecordStreamIndex].CreateReader(); - while (reader.CanRead(sizeof(ushort) * 2)) - result.Add(CodeViewSymbol.FromReader(ReaderContext, ref reader)); + return SymbolStreamReader.ReadSymbols(ReaderContext, ref reader); + } + + /// + protected override IList GetModules() + { + var result = new List(); + + for (int i = 0; i < DbiStream.Modules.Count; i++) + { + var descriptor = DbiStream.Modules[i]; + var modi = GetModiStream(descriptor); + var sourceFiles = i < DbiStream.SourceFiles.Count + ? DbiStream.SourceFiles[i] + : null; + + result.Add(new SerializedPdbModule(ReaderContext, descriptor, sourceFiles, modi)); + } return result; } + + private ModiStream? GetModiStream(ModuleDescriptor descriptor) + { + int index = descriptor.SymbolStreamIndex; + if (index >= _file.Streams.Count) + return null; + + var stream = _file.Streams[index]; + return ModiStream.FromReader(stream.CreateReader(), descriptor); + } + + private class LeafStreamCache + { + private readonly PdbReaderContext _context; + private readonly TpiStream _stream; + private ICodeViewLeaf?[]? _leaves; + + public LeafStreamCache(PdbReaderContext context, TpiStream stream) + { + _context = context; + _stream = stream; + } + + [MemberNotNull(nameof(_leaves))] + private void EnsureLeavesInitialized() + { + if (_leaves is null) + { + Interlocked.CompareExchange(ref _leaves, + new ICodeViewLeaf?[_stream.TypeIndexEnd - _stream.TypeIndexBegin], null); + } + } + + public IEnumerable GetRecords() + { + for (uint typeIndex = _stream.TypeIndexBegin; typeIndex < _stream.TypeIndexEnd; typeIndex++) + { + if (TryGetRecord(typeIndex, out var leaf)) + yield return leaf; + } + } + + public bool TryGetRecord(uint typeIndex, [NotNullWhen(true)] out ICodeViewLeaf? leaf) + { + if (typeIndex < _stream.TypeIndexBegin) + { + leaf = null; + return false; + } + + EnsureLeavesInitialized(); + + if (typeIndex >= _stream.TypeIndexBegin && typeIndex < _stream.TypeIndexEnd) + { + leaf = _leaves[typeIndex - _stream.TypeIndexBegin]; + if (leaf is null && _stream.TryGetLeafRecordReader(typeIndex, out var reader)) + { + leaf = CodeViewLeaf.FromReader(_context, typeIndex, ref reader); + Interlocked.CompareExchange(ref _leaves[typeIndex - _stream.TypeIndexBegin], leaf, null); + } + + leaf = _leaves[typeIndex - _stream.TypeIndexBegin]; + return leaf is not null; + } + + leaf = null; + return false; + } + + } } diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbModule.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbModule.cs new file mode 100644 index 000000000..43cd0c90a --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbModule.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; +using AsmResolver.Symbols.Pdb.Metadata.Modi; +using AsmResolver.Symbols.Pdb.Records; +using AsmResolver.Symbols.Pdb.Records.Serialized; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides an implementation for a PDB module that is read from an input PDB image. +/// +public class SerializedPdbModule : PdbModule +{ + private readonly PdbReaderContext _context; + private readonly ModuleDescriptor _descriptor; + private readonly ModiStream? _stream; + + /// + /// Reads a module from a PDB image based on a module descriptor in the DBI stream and a MoDi stream. + /// + /// The reading context in which the module is situated in. + /// The module descriptor as described in the DBI stream. + /// The source files as described in the DBI stream. + /// The MoDi stream to read symbols from. + public SerializedPdbModule( + PdbReaderContext context, + ModuleDescriptor descriptor, + SourceFileCollection? sourceFiles, + ModiStream? stream) + { + _context = context; + _descriptor = descriptor; + _stream = stream; + + if (sourceFiles is not null) + { + foreach (var file in sourceFiles) + SourceFiles.Add(new PdbSourceFile(file)); + } + } + + /// + protected override Utf8String? GetName() => _descriptor.ModuleName; + + /// + protected override Utf8String? GetObjectFileName() => _descriptor.ObjectFileName; + + /// + protected override SectionContribution? GetSectionContribution() => _descriptor.SectionContribution; + + /// + protected override IList GetSymbols() + { + if (_stream?.Symbols is null) + return new List(); + + var reader = _stream.Symbols.CreateReader(); + return SymbolStreamReader.ReadSymbols(_context, ref reader); + } +} diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index c83d52de8..5ce11d3bf 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/test/AsmResolver.Benchmarks/Program.cs b/test/AsmResolver.Benchmarks/Program.cs index c56e6149e..af6c4b907 100644 --- a/test/AsmResolver.Benchmarks/Program.cs +++ b/test/AsmResolver.Benchmarks/Program.cs @@ -55,8 +55,10 @@ public static async Task Main(string[] args) if (string.IsNullOrEmpty(benchmarkType)) BenchmarkRunner.Run(Assembly.GetExecutingAssembly(), config); + else if (Type.GetType($"AsmResolver.Benchmarks.{benchmarkType}") is { } type) + BenchmarkRunner.Run(type, config); else - BenchmarkRunner.Run(Type.GetType($"AsmResolver.Benchmarks.{benchmarkType}"), config); + Console.Error.WriteLine($"Could not find benchmark {benchmarkType}."); }, baselineVersionOption, onlyOption); diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj index c62d0b8c7..b71b3c0a6 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -8,10 +8,10 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index a3983706c..40011e503 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -9,7 +9,7 @@ - + all diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs index 7eff9300e..ae699bc43 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs @@ -99,5 +99,26 @@ public void PreserveDuplicatedTypeRefs() newReferences.Select(r => r.MetadataToken).ToHashSet()); } + [Fact] + public void PreserveMethodDefinitionParents() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.ArgListTest); + var reference = (MemberReference) module.ManagedEntryPointMethod!.CilMethodBody! + .Instructions.First(i => i.OpCode.Code == CilCode.Call) + .Operand!; + + + var newModule = RebuildAndReloadModule(module, + MetadataBuilderFlags.PreserveMemberReferenceIndices + | MetadataBuilderFlags.PreserveMethodDefinitionIndices); + var newReference = (MemberReference) newModule.ManagedEntryPointMethod!.CilMethodBody! + .Instructions.First(i => i.OpCode.Code == CilCode.Call) + .Operand!; + + Assert.IsAssignableFrom(reference.Parent); + Assert.IsAssignableFrom(newReference.Parent); + Assert.Equal(reference.Parent.Name, newReference.Parent.Name); + } + } } diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index ffc339182..1f84d6daa 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -7,6 +7,7 @@ using AsmResolver.DotNet.Bundles; using AsmResolver.IO; using AsmResolver.PE; +using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.File; using AsmResolver.PE.File.Headers; using AsmResolver.PE.Win32Resources.Version; @@ -120,10 +121,9 @@ public void WriteWithSubSystem(SubSystem subSystem) string appHostTemplatePath = FindAppHostTemplate("6.0"); using var stream = new MemoryStream(); - manifest.WriteUsingTemplate(stream, new BundlerParameters(appHostTemplatePath, "HelloWorld.dll") - { - SubSystem = subSystem - }); + var parameters = BundlerParameters.FromTemplate(appHostTemplatePath, "HelloWorld.dll"); + parameters.SubSystem = subSystem; + manifest.WriteUsingTemplate(stream, parameters); var newFile = PEFile.FromBytes(stream.ToArray()); Assert.Equal(subSystem, newFile.OptionalHeader.SubSystem); @@ -143,7 +143,7 @@ public void WriteWithWin32Resources() // Bundle with PE image as template for PE headers and resources. using var stream = new MemoryStream(); - manifest.WriteUsingTemplate(stream, new BundlerParameters( + manifest.WriteUsingTemplate(stream, BundlerParameters.FromTemplate( File.ReadAllBytes(appHostTemplatePath), "HelloWorld.dll", oldImage)); @@ -186,7 +186,7 @@ public void NewManifestShouldGenerateBundleIdIfUnset() Assert.Null(manifest.BundleID); using var stream = new MemoryStream(); - manifest.WriteUsingTemplate(stream, new BundlerParameters( + manifest.WriteUsingTemplate(stream, BundlerParameters.FromTemplate( FindAppHostTemplate("6.0"), "HelloWorld.dll")); @@ -205,6 +205,71 @@ public void SameManifestContentsShouldResultInSameBundleID() Assert.Equal(manifest.BundleID, newManifest.GenerateDeterministicBundleID()); } + [Fact] + public void PatchAndRepackageExistingBundleV1() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + AssertPatchAndRepackageChangesOutput(Properties.Resources.HelloWorld_SingleFile_V1); + } + + [Fact] + public void PatchAndRepackageExistingBundleV2() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + AssertPatchAndRepackageChangesOutput(Properties.Resources.HelloWorld_SingleFile_V2); + } + + [Fact] + public void PatchAndRepackageExistingBundleV6() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + AssertPatchAndRepackageChangesOutput(Properties.Resources.HelloWorld_SingleFile_V6); + } + + private void AssertPatchAndRepackageChangesOutput( + byte[] original, + [CallerFilePath] string className = "File", + [CallerMemberName] string methodName = "Method") + { + // Read manifest and locate main entry point file. + var manifest = BundleManifest.FromBytes(original); + var mainFile = manifest.Files.First(f => f.RelativePath.Contains("HelloWorld.dll")); + + // Patch entry point file. + var module = ModuleDefinition.FromBytes(mainFile.GetData()); + module.ManagedEntryPointMethod!.CilMethodBody! + .Instructions.First(i => i.OpCode.Code == CilCode.Ldstr) + .Operand = "Hello, Mars!"; + + using var moduleStream = new MemoryStream(); + module.Write(moduleStream); + + mainFile.Contents = new DataSegment(moduleStream.ToArray()); + mainFile.IsCompressed = false; + + manifest.BundleID = manifest.GenerateDeterministicBundleID(); + + // Repackage bundle using existing bundle as template. + using var bundleStream = new MemoryStream(); + manifest.WriteUsingTemplate(bundleStream, BundlerParameters.FromExistingBundle( + original, + mainFile.RelativePath)); + + // Verify application runs as expected. + DeleteTempExtractionDirectory(manifest, "HelloWorld.dll"); + string output = _fixture + .GetRunner() + .RunAndCaptureOutput( + "HelloWorld.exe", + bundleStream.ToArray(), + null, + 5000, + className, + methodName); + + Assert.Equal($"Hello, Mars!{Environment.NewLine}", output); + } + private void AssertWriteManifestWindowsPreservesOutput( BundleManifest manifest, string sdkVersion, @@ -214,9 +279,10 @@ private void AssertWriteManifestWindowsPreservesOutput( [CallerMemberName] string methodName = "Method") { string appHostTemplatePath = FindAppHostTemplate(sdkVersion); + DeleteTempExtractionDirectory(manifest, fileName); using var stream = new MemoryStream(); - manifest.WriteUsingTemplate(stream, new BundlerParameters(appHostTemplatePath, fileName)); + manifest.WriteUsingTemplate(stream, BundlerParameters.FromTemplate(appHostTemplatePath, fileName)); var newManifest = BundleManifest.FromBytes(stream.ToArray()); AssertBundlesAreEqual(manifest, newManifest); @@ -233,6 +299,15 @@ private void AssertWriteManifestWindowsPreservesOutput( Assert.Equal(expectedOutput, output); } + private static void DeleteTempExtractionDirectory(BundleManifest manifest, string fileName) + { + if (manifest.MajorVersion != 1 || manifest.BundleID is null || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; + + string tempPath = Path.Combine(Path.GetTempPath(), ".net", Path.GetFileNameWithoutExtension(fileName), manifest.BundleID); + if (Directory.Exists(tempPath)) + Directory.Delete(tempPath, true); + } private static string FindAppHostTemplate(string sdkVersion) { diff --git a/test/AsmResolver.DotNet.Tests/Collections/ParameterCollectionTest.cs b/test/AsmResolver.DotNet.Tests/Collections/ParameterCollectionTest.cs index 847c046a1..8dda148a2 100644 --- a/test/AsmResolver.DotNet.Tests/Collections/ParameterCollectionTest.cs +++ b/test/AsmResolver.DotNet.Tests/Collections/ParameterCollectionTest.cs @@ -4,6 +4,7 @@ using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Methods; using AsmResolver.PE.DotNet.Metadata.Strings; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; namespace AsmResolver.DotNet.Tests.Collections @@ -24,6 +25,13 @@ private static MethodDefinition ObtainInstanceTestMethod(string name) return type.Methods.First(m => m.Name == name); } + private static MethodDefinition ObtainGenericInstanceTestMethod(string name) + { + var module = ModuleDefinition.FromFile(typeof(GenericInstanceMethods<,>).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == "GenericInstanceMethods`2"); + return type.Methods.First(m => m.Name == name); + } + [Fact] public void ReadEmptyParametersFromStaticMethod() { @@ -111,6 +119,23 @@ public void ReadMultipleParametersFromInstanceMethod() Assert.Equal(nameof(InstanceMethods), method.Parameters.ThisParameter.ParameterType.Name); } + [Fact] + public void ReadEmptyParametersFromGenericInstanceMethod() + { + var method = ObtainGenericInstanceTestMethod(nameof(GenericInstanceMethods.InstanceParameterlessMethod)); + Assert.Empty(method.Parameters); + Assert.NotNull(method.Parameters.ThisParameter); + var genericInstanceType = Assert.IsAssignableFrom(method.Parameters.ThisParameter.ParameterType); + Assert.Equal("GenericInstanceMethods`2", genericInstanceType.GenericType.Name); + Assert.Equal(2, genericInstanceType.TypeArguments.Count); + Assert.All(genericInstanceType.TypeArguments, (typeArgument, i) => + { + var genericParameterSignature = Assert.IsAssignableFrom(typeArgument); + Assert.Equal(GenericParameterType.Type, genericParameterSignature.ParameterType); + Assert.Equal(i, genericParameterSignature.Index); + }); + } + [Fact] public void ReadReturnTypeFromStaticParameterlessMethod() { @@ -190,5 +215,48 @@ public void UnnamedParameterShouldResultInDummyName() param.Name = null; Assert.All(method.Parameters, p => Assert.Equal(p.Name, $"A_{p.MethodSignatureIndex}")); } + + [Fact] + public void GetOrCreateDefinitionShouldCreateNewDefinition() + { + var dummyModule = new ModuleDefinition("TestModule"); + var corLibTypesFactory = dummyModule.CorLibTypeFactory; + var method = new MethodDefinition("TestMethodNoParameterDefinitions", + MethodAttributes.Public | MethodAttributes.Static, + MethodSignature.CreateStatic(corLibTypesFactory.Void, corLibTypesFactory.Int32)); + + var param = Assert.Single(method.Parameters); + + Assert.Null(param.Definition); + var definition = param.GetOrCreateDefinition(); + + Assert.Equal(param.Sequence, definition.Sequence); + Assert.Equal(Utf8String.Empty, definition.Name); + Assert.Equal((ParameterAttributes)0, definition.Attributes); + Assert.Contains(definition, method.ParameterDefinitions); + Assert.Same(definition, param.Definition); + } + + [Fact] + public void GetOrCreateDefinitionShouldReturnExistingDefinition() + { + var method = ObtainStaticTestMethod(nameof(MultipleMethods.SingleParameterMethod)); + + var param = Assert.Single(method.Parameters); + + var existingDefinition = param.Definition; + Assert.NotNull(existingDefinition); + var definition = param.GetOrCreateDefinition(); + Assert.Same(existingDefinition, definition); + } + + [Fact] + public void GetOrCreateDefinitionThrowsOnVirtualThisParameter() + { + var method = ObtainInstanceTestMethod(nameof(InstanceMethods.InstanceParameterlessMethod)); + + Assert.NotNull(method.Parameters.ThisParameter); + Assert.Throws(() => method.Parameters.ThisParameter.GetOrCreateDefinition()); + } } } diff --git a/test/AsmResolver.DotNet.Tests/MemberReferenceTest.cs b/test/AsmResolver.DotNet.Tests/MemberReferenceTest.cs index 4fcfa3f56..fce5f5ec7 100644 --- a/test/AsmResolver.DotNet.Tests/MemberReferenceTest.cs +++ b/test/AsmResolver.DotNet.Tests/MemberReferenceTest.cs @@ -9,12 +9,12 @@ public class MemberReferenceTest public void ResolveForwardedMethod() { var module = ModuleDefinition.FromBytes(Properties.Resources.ForwarderRefTest); - var forwarder = ModuleDefinition.FromBytes(Properties.Resources.ForwarderLibrary).Assembly; - var library = ModuleDefinition.FromBytes(Properties.Resources.ActualLibrary).Assembly; + var forwarder = ModuleDefinition.FromBytes(Properties.Resources.ForwarderLibrary).Assembly!; + var library = ModuleDefinition.FromBytes(Properties.Resources.ActualLibrary).Assembly!; module.MetadataResolver.AssemblyResolver.AddToCache(forwarder, forwarder); module.MetadataResolver.AssemblyResolver.AddToCache(library, library); - forwarder.ManifestModule.MetadataResolver.AssemblyResolver.AddToCache(library, library); + forwarder.ManifestModule!.MetadataResolver.AssemblyResolver.AddToCache(library, library); var reference = module .GetImportedMemberReferences() @@ -24,4 +24,4 @@ public void ResolveForwardedMethod() Assert.NotNull(definition); } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index 76e9b61fd..db9a460ae 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using AsmResolver.DotNet.Builder; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.IO; @@ -169,7 +170,7 @@ public void LookupTypeReferenceStronglyTyped() public void TryLookupTypeReferenceStronglyTyped() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); - + Assert.True(module.TryLookupMember(new MetadataToken(TableIndex.TypeRef, 12), out TypeReference reference)); Assert.Equal("System", reference.Namespace); Assert.Equal("Object", reference.Name); diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index 427f830ea..f6b0b4cf9 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -359,5 +359,12 @@ public static byte[] ModuleCctorNetFramework { return ((byte[])(obj)); } } + + public static byte[] ArgListTest { + get { + object obj = ResourceManager.GetObject("ArgListTest", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index f13ac4af0..06cc5cceb 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -153,4 +153,7 @@ ..\Resources\ModuleCctorNetFramework.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\ArgListTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.DotNet.Tests/Resources/ArgListTest.exe b/test/AsmResolver.DotNet.Tests/Resources/ArgListTest.exe new file mode 100644 index 000000000..4b84fe4df Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/ArgListTest.exe differ diff --git a/test/AsmResolver.DotNet.Tests/Signatures/MethodSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/MethodSignatureTest.cs index ed2b51265..8cab7f8e5 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/MethodSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/MethodSignatureTest.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; @@ -50,5 +51,28 @@ public void MakeGenericStaticShouldNotHaveHasThisAndGenericFlagSet() Assert.False(signature.HasThis); Assert.True(signature.IsGeneric); } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SentinelParameterTypes(bool rebuild) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.ArgListTest); + if (rebuild) + { + using var stream = new MemoryStream(); + module.Write(stream); + module = ModuleDefinition.FromBytes(stream.ToArray()); + } + + var reference = (MemberReference) module.ManagedEntryPointMethod!.CilMethodBody! + .Instructions.First(i => i.OpCode.Code == CilCode.Call) + .Operand!; + + var signature = Assert.IsAssignableFrom(reference.Signature); + var type = Assert.Single(signature.SentinelParameterTypes); + + Assert.Equal(module.CorLibTypeFactory.String, type, SignatureComparer.Default); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index 69f25cbe7..5c26a3518 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -1,5 +1,9 @@ +using System; +using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.DotNet.TestCases.Generics; +using AsmResolver.DotNet.TestCases.Types; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -122,5 +126,498 @@ public void GetOptionalModifierTypeFullName() .MakeModifierType(_dummyType, false) .FullName); } + + [Fact] + public void StripModifiersPinnedType() + { + var type = _dummyType.ToTypeSignature(); + Assert.Equal(type, type.MakePinnedType().StripModifiers(), SignatureComparer.Default); + } + + [Fact] + public void StripModifiersCustomModifierType() + { + var type = _dummyType.ToTypeSignature(); + Assert.Equal(type, type.MakeModifierType(_dummyType, false).StripModifiers(), SignatureComparer.Default); + } + + [Fact] + public void StripMultipleModifiers() + { + var type = _dummyType.ToTypeSignature(); + Assert.Equal(type, + type + .MakeModifierType(_dummyType, false) + .MakeModifierType(_dummyType, true) + .MakePinnedType() + .StripModifiers(), + SignatureComparer.Default); + } + + [Theory] + [InlineData(ElementType.I, ElementType.I)] + [InlineData(ElementType.I1, ElementType.I1)] + [InlineData(ElementType.I2, ElementType.I2)] + [InlineData(ElementType.I4, ElementType.I4)] + [InlineData(ElementType.I8, ElementType.I8)] + [InlineData(ElementType.U, ElementType.I)] + [InlineData(ElementType.U1, ElementType.I1)] + [InlineData(ElementType.U2, ElementType.I2)] + [InlineData(ElementType.U4, ElementType.I4)] + [InlineData(ElementType.U8, ElementType.I8)] + [InlineData(ElementType.String, ElementType.String)] + [InlineData(ElementType.Boolean, ElementType.Boolean)] + [InlineData(ElementType.Char, ElementType.Char)] + public void GetReducedTypeOfPrimitive(ElementType type, ElementType expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(expected, module.CorLibTypeFactory.FromElementType(type)!.GetReducedType().ElementType); + } + + [Theory] + [InlineData(typeof(Int32Enum), ElementType.I4)] + [InlineData(typeof(Int64Enum), ElementType.I8)] + public void GetReducedTypeOfEnum(Type type, ElementType expected) + { + var module = ModuleDefinition.FromFile(type.Assembly.Location); + var signature = module.LookupMember(type.MetadataToken).ToTypeSignature(); + Assert.Equal(expected, signature.GetReducedType().ElementType); + } + + [Fact] + public void GetReducedTypeOfNonPrimitive() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.TopLevelTypes.First(t => t.Name == "Program").ToTypeSignature(false); + Assert.Equal(type, type.GetReducedType()); + } + + [Theory] + [InlineData(ElementType.I, ElementType.I)] + [InlineData(ElementType.I1, ElementType.I1)] + [InlineData(ElementType.I2, ElementType.I2)] + [InlineData(ElementType.I4, ElementType.I4)] + [InlineData(ElementType.I8, ElementType.I8)] + [InlineData(ElementType.U, ElementType.I)] + [InlineData(ElementType.U1, ElementType.I1)] + [InlineData(ElementType.U2, ElementType.I2)] + [InlineData(ElementType.U4, ElementType.I4)] + [InlineData(ElementType.U8, ElementType.I8)] + [InlineData(ElementType.String, ElementType.String)] + [InlineData(ElementType.Boolean, ElementType.I1)] + [InlineData(ElementType.Char, ElementType.I2)] + public void GetVerificationTypeOfPrimitive(ElementType type, ElementType expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(expected, module.CorLibTypeFactory.FromElementType(type)!.GetVerificationType().ElementType); + } + + [Theory] + [InlineData(ElementType.I, ElementType.I)] + [InlineData(ElementType.I1, ElementType.I1)] + [InlineData(ElementType.I2, ElementType.I2)] + [InlineData(ElementType.I4, ElementType.I4)] + [InlineData(ElementType.I8, ElementType.I8)] + [InlineData(ElementType.U, ElementType.I)] + [InlineData(ElementType.U1, ElementType.I1)] + [InlineData(ElementType.U2, ElementType.I2)] + [InlineData(ElementType.U4, ElementType.I4)] + [InlineData(ElementType.U8, ElementType.I8)] + [InlineData(ElementType.String, ElementType.String)] + [InlineData(ElementType.Boolean, ElementType.I1)] + [InlineData(ElementType.Char, ElementType.I2)] + public void GetVerificationTypeOfManagedPrimitivePointer(ElementType type, ElementType expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var pointerType = module.CorLibTypeFactory.FromElementType(type)!.MakeByReferenceType(); + var actualType = Assert.IsAssignableFrom(pointerType.GetVerificationType()); + Assert.Equal(expected, actualType.BaseType.ElementType); + } + + [Fact] + public void GetVerificationTypeOfNonPrimitive() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.TopLevelTypes.First(t => t.Name == "Program").ToTypeSignature(false); + Assert.Equal(type, type.GetVerificationType()); + } + + [Theory] + [InlineData(ElementType.I, ElementType.I)] + [InlineData(ElementType.I1, ElementType.I4)] + [InlineData(ElementType.I2, ElementType.I4)] + [InlineData(ElementType.I4, ElementType.I4)] + [InlineData(ElementType.I8, ElementType.I8)] + [InlineData(ElementType.U, ElementType.I)] + [InlineData(ElementType.U1, ElementType.I4)] + [InlineData(ElementType.U2, ElementType.I4)] + [InlineData(ElementType.U4, ElementType.I4)] + [InlineData(ElementType.U8, ElementType.I8)] + [InlineData(ElementType.String, ElementType.String)] + [InlineData(ElementType.Boolean, ElementType.I4)] + [InlineData(ElementType.Char, ElementType.I4)] + [InlineData(ElementType.R4, ElementType.R8)] // Technically incorrect, as it should be F. + [InlineData(ElementType.R8, ElementType.R8)] + public void GetIntermediateTypeOfPrimitive(ElementType type, ElementType expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(expected, module.CorLibTypeFactory.FromElementType(type)!.GetIntermediateType().ElementType); + } + + [Fact] + public void GetIntermediateTypeOfNonPrimitive() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.TopLevelTypes.First(t => t.Name == "Program").ToTypeSignature(false); + Assert.Equal(type, type.GetIntermediateType()); + } + + [Fact] + public void GetDirectBaseClassOfArrayType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var signature = module.CorLibTypeFactory.Object.MakeArrayType(3); + Assert.Equal("System.Array", signature.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfSzArrayType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var signature = module.CorLibTypeFactory.Object.MakeSzArrayType(); + Assert.Equal("System.Array", signature.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfInterfaceType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var interfaceDefinition = new TypeDefinition("Namespace", "IName", TypeAttributes.Interface); + module.TopLevelTypes.Add(interfaceDefinition); + + var interfaceSignature = interfaceDefinition.ToTypeSignature(false); + + Assert.Equal("System.Object", interfaceSignature.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfNormalType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.TopLevelTypes.First(t => t.Name == "Program").ToTypeSignature(); + Assert.Equal("System.Object", type.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfNormalTypeWithBaseType() + { + var module = ModuleDefinition.FromFile(typeof(DerivedClass).Assembly.Location); + var type = module.LookupMember(typeof(DerivedClass).MetadataToken); + Assert.Equal(type.BaseType!.FullName, type.ToTypeSignature().GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfGenericTypeInstance() + { + var module = ModuleDefinition.FromFile(typeof(GenericType<,,>).Assembly.Location); + var genericInstanceType = module.LookupMember(typeof(GenericType<,,>).MetadataToken) + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.String, + module.CorLibTypeFactory.Object); + + Assert.Equal("System.Object", genericInstanceType.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfGenericTypeInstanceWithGenericBaseClass() + { + var module = ModuleDefinition.FromFile(typeof(GenericDerivedType<,>).Assembly.Location); + var genericInstanceType = module.LookupMember(typeof(GenericDerivedType<,>).MetadataToken) + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.Object); + + var baseClass = Assert.IsAssignableFrom( + genericInstanceType.GetDirectBaseClass()); + + Assert.Equal(typeof(GenericType<,,>).Namespace, baseClass.GenericType.Namespace); + Assert.Equal(typeof(GenericType<,,>).Name, baseClass.GenericType.Name); + Assert.Equal(new[] + { + "System.Int32", + "System.Object", + "System.String" + }, baseClass.TypeArguments.Select(t => t.FullName)); + } + + [Theory] + [InlineData(ElementType.I)] + [InlineData(ElementType.I1)] + [InlineData(ElementType.I2)] + [InlineData(ElementType.I4)] + [InlineData(ElementType.I8)] + [InlineData(ElementType.U)] + [InlineData(ElementType.U1)] + [InlineData(ElementType.U2)] + [InlineData(ElementType.U4)] + [InlineData(ElementType.U8)] + [InlineData(ElementType.R4)] + [InlineData(ElementType.R8)] + [InlineData(ElementType.String)] + [InlineData(ElementType.Boolean)] + [InlineData(ElementType.Char)] + public void IsCompatibleWithIdenticalPrimitiveTypes(ElementType elementType) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.CorLibTypeFactory.FromElementType(elementType)!; + Assert.True(type.IsCompatibleWith(type)); + } + + [Theory] + [InlineData(typeof(AbstractClass))] + [InlineData(typeof(DerivedClass))] + public void IsCompatibleWithIdenticalUserTypes(Type type) + { + var module = ModuleDefinition.FromFile(type.Assembly.Location); + var signature = module.LookupMember(type.MetadataToken).ToTypeSignature(); + Assert.True(signature.IsCompatibleWith(signature)); + } + + [Theory] + [InlineData(typeof(DerivedClass), typeof(AbstractClass), true)] + [InlineData(typeof(DerivedDerivedClass), typeof(DerivedClass), true)] + [InlineData(typeof(DerivedDerivedClass), typeof(AbstractClass), true)] + [InlineData(typeof(AbstractClass), typeof(DerivedClass), false)] + [InlineData(typeof(AbstractClass), typeof(DerivedDerivedClass), false)] + public void IsCompatibleWithBaseClass(Type derivedType, Type baseType, bool expected) + { + var module = ModuleDefinition.FromFile(derivedType.Assembly.Location); + var derivedSignature = module.LookupMember(derivedType.MetadataToken).ToTypeSignature(); + var abstractSignature = module.LookupMember(baseType.MetadataToken).ToTypeSignature(); + Assert.Equal(expected, derivedSignature.IsCompatibleWith(abstractSignature)); + } + + [Theory] + [InlineData(typeof(InterfaceImplementations), typeof(IInterface1), true)] + [InlineData(typeof(InterfaceImplementations), typeof(IInterface2), true)] + [InlineData(typeof(InterfaceImplementations), typeof(IInterface3), false)] + [InlineData(typeof(InterfaceImplementations), typeof(IInterface4), false)] + [InlineData(typeof(DerivedInterfaceImplementations), typeof(IInterface1), true)] + [InlineData(typeof(DerivedInterfaceImplementations), typeof(IInterface2), true)] + [InlineData(typeof(DerivedInterfaceImplementations), typeof(IInterface3), true)] + [InlineData(typeof(DerivedInterfaceImplementations), typeof(IInterface4), false)] + [InlineData(typeof(IInterface1), typeof(InterfaceImplementations), false)] + [InlineData(typeof(IInterface2), typeof(InterfaceImplementations), false)] + [InlineData(typeof(IInterface3), typeof(DerivedInterfaceImplementations), false)] + [InlineData(typeof(IInterface4), typeof(DerivedInterfaceImplementations), false)] + public void IsCompatibleWithInterface(Type derivedType, Type interfaceType, bool expected) + { + var module = ModuleDefinition.FromFile(typeof(DerivedClass).Assembly.Location); + var derivedSignature = module.LookupMember(derivedType.MetadataToken).ToTypeSignature(); + var interfaceSignature = module.LookupMember(interfaceType.MetadataToken).ToTypeSignature(); + Assert.Equal(expected, derivedSignature.IsCompatibleWith(interfaceSignature)); + } + + [Theory] + [InlineData(new[] { ElementType.I4, ElementType.I4 }, new[] { ElementType.I4, ElementType.I4, ElementType.String }, true)] + [InlineData(new[] { ElementType.I4, ElementType.I8 }, new[] { ElementType.I4, ElementType.I4, ElementType.String }, false)] + [InlineData(new[] { ElementType.I4, ElementType.I4 }, new[] { ElementType.I4, ElementType.I8, ElementType.String }, false)] + [InlineData(new[] { ElementType.I4, ElementType.I4 }, new[] { ElementType.I4, ElementType.I4, ElementType.Boolean }, false)] + public void IsCompatibleWithGenericInterface(ElementType[] typeArguments1, ElementType[] typeArguments2, bool expected) + { + var module = ModuleDefinition.FromFile(typeof(GenericInterfaceImplementation<,>).Assembly.Location); + + var type1 = module.LookupMember(typeof(GenericInterfaceImplementation<,>).MetadataToken) + .ToTypeSignature(false) + .MakeGenericInstanceType( + typeArguments1.Select(x => (TypeSignature) module.CorLibTypeFactory.FromElementType(x)!).ToArray() + ); + + var type2 = module.LookupMember(typeof(IGenericInterface<,,>).MetadataToken) + .ToTypeSignature(false) + .MakeGenericInstanceType( + typeArguments2.Select(x => (TypeSignature) module.CorLibTypeFactory.FromElementType(x)!).ToArray() + ); + + Assert.Equal(expected, type1.IsCompatibleWith(type2)); + } + + [Theory] + [InlineData(ElementType.I1, ElementType.I1, true)] + [InlineData(ElementType.U1, ElementType.I1, true)] + [InlineData(ElementType.I1, ElementType.U1, true)] + [InlineData(ElementType.U1, ElementType.U1, true)] + [InlineData(ElementType.I1, ElementType.U2, false)] + [InlineData(ElementType.U2, ElementType.U1, false)] + [InlineData(ElementType.I4, ElementType.I4, true)] + [InlineData(ElementType.I4, ElementType.U4, true)] + [InlineData(ElementType.U4, ElementType.I4, true)] + public void IsCompatibleWithArray(ElementType elementType1, ElementType elementType2, bool expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type1 = module.CorLibTypeFactory.FromElementType(elementType1)!.MakeSzArrayType(); + var type2 = module.CorLibTypeFactory.FromElementType(elementType2)!.MakeSzArrayType(); + Assert.Equal(expected, type1.IsCompatibleWith(type2)); + } + + [Theory] + [InlineData(ElementType.I1, ElementType.I1, true)] + [InlineData(ElementType.U1, ElementType.I1, true)] + [InlineData(ElementType.I1, ElementType.U1, true)] + [InlineData(ElementType.U1, ElementType.U1, true)] + [InlineData(ElementType.I1, ElementType.U2, false)] + [InlineData(ElementType.U2, ElementType.U1, false)] + [InlineData(ElementType.I4, ElementType.I4, true)] + [InlineData(ElementType.I4, ElementType.U4, true)] + [InlineData(ElementType.U4, ElementType.I4, true)] + public void IsCompatibleWithArrayAndIList(ElementType elementType1, ElementType elementType2, bool expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.FromElementType(elementType1)!.MakeSzArrayType(); + var type2 = module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System.Collections.Generic", "IList`1") + .ToTypeSignature(false) + .MakeGenericInstanceType(module.CorLibTypeFactory.FromElementType(elementType2)!); + + Assert.Equal(expected, type1.IsCompatibleWith(type2)); + } + + [Fact] + public void IsCompatibleWithGenericInstanceAndObject() + { + var module = ModuleDefinition.FromFile(typeof(GenericType<,,>).Assembly.Location); + + var type1 = module + .LookupMember(typeof(GenericType<,,>).MetadataToken) + .ToTypeSignature() + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.Object, + module.CorLibTypeFactory.String); + + Assert.True(type1.IsCompatibleWith(type1)); + Assert.True(type1.IsCompatibleWith(module.CorLibTypeFactory.Object)); + } + + [Fact] + public void IsCompatibleWithGenericInstance() + { + var module = ModuleDefinition.FromFile(typeof(GenericDerivedType<,>).Assembly.Location); + + var type1 = module + .LookupMember(typeof(GenericDerivedType<,>).MetadataToken) + .ToTypeSignature() + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.Object); + + var type2 = module + .LookupMember(typeof(GenericType<,,>).MetadataToken) + .ToTypeSignature() + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.Object, + module.CorLibTypeFactory.String); + + var type3 = module + .LookupMember(typeof(GenericType<,,>).MetadataToken) + .ToTypeSignature() + .MakeGenericInstanceType( + module.CorLibTypeFactory.Object, + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.String); + + Assert.True(type1.IsCompatibleWith(type2)); + Assert.False(type1.IsCompatibleWith(type3)); + } + + [Theory] + [InlineData(ElementType.I1, ElementType.I1, true)] + [InlineData(ElementType.U1, ElementType.I1, true)] + [InlineData(ElementType.I1, ElementType.U1, true)] + [InlineData(ElementType.I1, ElementType.Boolean, true)] + [InlineData(ElementType.I2, ElementType.Char, true)] + [InlineData(ElementType.I4, ElementType.Boolean, false)] + [InlineData(ElementType.I1, ElementType.U2, false)] + public void IsCompatibleWithPointers(ElementType elementType1, ElementType elementType2, bool expected) + { + var module = ModuleDefinition.FromFile(typeof(GenericDerivedType<,>).Assembly.Location); + + var type1 = module.CorLibTypeFactory.FromElementType(elementType1)!.MakePointerType(); + var type2 = module.CorLibTypeFactory.FromElementType(elementType2)!.MakePointerType(); + + Assert.Equal(expected, type1.IsCompatibleWith(type2)); + } + + [Theory] + [InlineData(ElementType.I1, ElementType.I4, true)] + [InlineData(ElementType.I2, ElementType.I4, true)] + [InlineData(ElementType.I4, ElementType.I4, true)] + [InlineData(ElementType.I8, ElementType.I4, false)] + [InlineData(ElementType.I4, ElementType.I1, true)] + [InlineData(ElementType.I4, ElementType.I2, true)] + [InlineData(ElementType.I4, ElementType.I8, false)] + [InlineData(ElementType.I, ElementType.I4, true)] + [InlineData(ElementType.I4, ElementType.I, true)] + public void IsAssignablePrimitives(ElementType elementType1, ElementType elementType2, bool expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.FromElementType(elementType1)!; + var type2 = module.CorLibTypeFactory.FromElementType(elementType2)!; + + Assert.Equal(expected, type1.IsAssignableTo(type2)); + } + + [Fact] + public void IgnoreCustomModifiers() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.Int32; + var type2 = module.CorLibTypeFactory.Int32.MakeModifierType(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System.Runtime.CompilerServices", "IsVolatile") + .ImportWith(module.DefaultImporter), + true); + + Assert.True(type1.IsCompatibleWith(type2)); + Assert.True(type2.IsCompatibleWith(type1)); + } + + [Fact] + public void IgnoreNestedCustomModifiers() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.Int32; + var type2 = module.CorLibTypeFactory.Int32.MakeModifierType(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System.Runtime.CompilerServices", "IsVolatile") + .ImportWith(module.DefaultImporter), + true); + + var genericType = module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System.Collections.Generic", "List`1") + .ImportWith(module.DefaultImporter); + + var genericType1 = genericType.MakeGenericInstanceType(type1); + var genericType2 = genericType.MakeGenericInstanceType(type2); + + Assert.True(genericType1.IsCompatibleWith(genericType2)); + Assert.True(genericType2.IsCompatibleWith(genericType1)); + } + + [Fact] + public void IgnorePinnedModifiers() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.Int32; + var type2 = module.CorLibTypeFactory.Int32.MakePinnedType(); + + Assert.True(type1.IsCompatibleWith(type2)); + Assert.True(type2.IsCompatibleWith(type1)); + } } } 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 80baee93b..a540f1edc 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -9,7 +9,7 @@ - + all diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index 2a656347a..d53d560b6 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index 3cdf923ca..9b7323361 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -8,7 +8,7 @@ - + all diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 942be7824..74743b9c8 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -8,9 +8,9 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BuildInfoLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BuildInfoLeafTest.cs new file mode 100644 index 000000000..1b2a3da58 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BuildInfoLeafTest.cs @@ -0,0 +1,30 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class BuildInfoLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public BuildInfoLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Entries() + { + var info = _fixture.SimplePdb.GetIdLeafRecord(0x100d); + + Assert.Equal(new[] + { + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll", + @"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.29.30133\bin\HostX86\x86\CL.exe", + @"dllmain.cpp", + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\Release\vc142.pdb", + @" Files (x86)\Windows Kits\10\Include\10.0.19041.0\winrt"" -external:I""C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\cppwinrt"" -external:I""C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\Include\um"" -X" + }, info.Entries.Select(e => e.Value.Value)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionIdentifierTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionIdentifierTest.cs new file mode 100644 index 000000000..26750c775 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FunctionIdentifierTest.cs @@ -0,0 +1,28 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class FunctionIdentifierTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public FunctionIdentifierTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + var leaf = _fixture.SimplePdb.GetIdLeafRecord(0x1453); + Assert.Equal("__get_entropy", leaf.Name); + } + + [Fact] + public void FunctionType() + { + var leaf = _fixture.SimplePdb.GetIdLeafRecord(0x1453); + Assert.IsAssignableFrom(leaf.FunctionType); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/StringIdentifierTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/StringIdentifierTest.cs new file mode 100644 index 000000000..33b4cf387 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/StringIdentifierTest.cs @@ -0,0 +1,48 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class StringIdentifierTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public StringIdentifierTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Value() + { + var id = _fixture.SimplePdb.GetIdLeafRecord(0x1000); + Assert.Equal(@"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll", id.Value); + } + + [Fact] + public void NoSubStrings() + { + var id = _fixture.SimplePdb.GetIdLeafRecord(0x1000); + Assert.Null(id.SubStrings); + } + + [Fact] + public void SubStrings() + { + var id = _fixture.SimplePdb.GetIdLeafRecord(0x100c); + var subStrings = id.SubStrings; + + Assert.NotNull(subStrings); + Assert.Equal(new[] + { + @"-c -Zi -nologo -W3 -WX- -diagnostics:column -sdl -O2 -Oi -Oy- -GL -DWIN32 -DNDEBUG -DSIMPLEDLL_EXPORTS -D_WINDOWS -D_USRDLL -D_WINDLL -D_UNICODE -DUNICODE -Gm- -EHs -EHc -MD -GS -Gy -fp:precise -permissive- -Zc:wchar_t -Zc:forScope", + @" -Zc:inline -Yupch.h -FpC:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\Release\SimpleDll.pch -external:W3 -Gd -TP -analyze- -FC -errorreport:prompt -I""C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.29.3013", + @"3\include"" -I""C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.29.30133\atlmfc\include"" -I""C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\VS\include"" -I""C:\Program Files (x86)\Windows", + @" Kits\10\Include\10.0.19041.0\ucrt"" -I""C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um"" -I""C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared"" -I""C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\winrt""", + @" -I""C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\cppwinrt"" -I""C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\Include\um"" -external:I""C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.29.30133\include""", + @" -external:I""C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.29.30133\atlmfc\include"" -external:I""C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\VS\include"" -external:I""C:\Program Files", + @" (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt"" -external:I""C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um"" -external:I""C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared"" -external:I""C:\Program", + }, subStrings.Entries.Select(x => x.Value.Value)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs index b1e8cfc2d..fae57a925 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs @@ -1,4 +1,8 @@ +using System; +using System.Linq; +using AsmResolver.PE.File.Headers; using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; using Xunit; namespace AsmResolver.Symbols.Pdb.Tests; @@ -12,6 +16,19 @@ public PdbImageTest(MockPdbFixture fixture) _fixture = fixture; } + [Fact] + public void BasicMetadata() + { + var image = _fixture.SimplePdb; + + Assert.Equal(1u, image.Age); + Assert.Equal(Guid.Parse("205dc366-d8f8-4175-8e06-26dd76722df5"), image.UniqueId); + Assert.Equal(DbiAttributes.None, image.Attributes); + Assert.Equal(MachineType.I386, image.Machine); + Assert.Equal(14, image.BuildMajorVersion); + Assert.Equal(29, image.BuildMinorVersion); + } + [Theory] [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] @@ -32,4 +49,44 @@ public void SimpleTypeLookupTwiceShouldCache() Assert.Same(type, type2); } + + [Fact] + public void ReadModules() + { + 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 *" + }, _fixture.SimplePdb.Modules.Select(m => m.Name!.Value)); + } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbModuleTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbModuleTest.cs new file mode 100644 index 000000000..2cdf36aa4 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbModuleTest.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests; + +public class PdbModuleTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PdbModuleTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + Assert.Equal("* CIL *", _fixture.SimplePdb.Modules[0].Name); + } + + [Fact] + public void ObjectFileName() + { + var module = _fixture.SimplePdb.Modules[4]; + Assert.Equal("Import:KERNEL32.dll", module.Name); + Assert.Equal( + @"C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\um\x86\kernel32.lib", + module.ObjectFileName); + } + + [Fact] + public void Children() + { + var module = _fixture.SimplePdb.Modules.First(m => m.Name!.Contains("dllmain.obj")); + Assert.Equal(new[] + { + CodeViewSymbolType.ObjName, + CodeViewSymbolType.Compile3, + CodeViewSymbolType.BuildInfo, + CodeViewSymbolType.GProc32, + }, module.Symbols.Select(x => x.CodeViewSymbolType)); + } + + [Fact] + public void SourceFiles() + { + var module = _fixture.SimplePdb.Modules.First(m => m.Name!.Contains("dllmain.obj")); + Assert.Equal(new Utf8String[] + { + "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" + }, module.SourceFiles.Select(f => f.FileName)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/BasePointerRelativeSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/BasePointerRelativeSymbolTest.cs new file mode 100644 index 000000000..19449456b --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/BasePointerRelativeSymbolTest.cs @@ -0,0 +1,39 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class BasePointerRelativeSymbolTest : IClassFixture +{ + private readonly BasePointerRelativeSymbol _symbol; + + public BasePointerRelativeSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("gs_report.obj")) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + } + + [Fact] + public void Name() + { + Assert.Equal("exception_pointers", _symbol.Name); + } + + [Fact] + public void Offset() + { + Assert.Equal(8, _symbol.Offset); + } + + [Fact] + public void Type() + { + Assert.IsAssignableFrom(_symbol.VariableType); + } + +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/BuildInfoSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/BuildInfoSymbolTest.cs new file mode 100644 index 000000000..cffeb7472 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/BuildInfoSymbolTest.cs @@ -0,0 +1,25 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class BuildInfoSymbolTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public BuildInfoSymbolTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Info() + { + var symbol = _fixture.SimplePdb.Modules[1].Symbols.OfType().First(); + var info = symbol.Info; + + Assert.NotNull(info); + Assert.Equal(5, info.Entries.Count); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/CallSiteSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/CallSiteSymbolTest.cs new file mode 100644 index 000000000..27e1f9aec --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/CallSiteSymbolTest.cs @@ -0,0 +1,32 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class CallSiteSymbolTest : IClassFixture +{ + private readonly CallSiteSymbol _symbol; + + public CallSiteSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("gs_report.obj")) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + } + + [Fact] + public void Offset() + { + Assert.Equal(0x0000037E, _symbol.Offset); + } + + [Fact] + public void FunctionType() + { + Assert.IsAssignableFrom(_symbol.FunctionType); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/CoffGroupSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/CoffGroupSymbolTest.cs new file mode 100644 index 000000000..77f21baaa --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/CoffGroupSymbolTest.cs @@ -0,0 +1,36 @@ +using System.Linq; +using AsmResolver.PE.File.Headers; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class CoffGroupSymbolTest : IClassFixture +{ + private readonly CoffGroupSymbol _symbol; + + public CoffGroupSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name == "* Linker *") + .Symbols.OfType() + .First(); + } + + [Fact] + public void BasicProperties() + { + Assert.Equal(1, _symbol.SegmentIndex); + Assert.Equal(0u, _symbol.Offset); + Assert.Equal(0xCE8u, _symbol.Size); + Assert.Equal( + SectionFlags.ContentCode | SectionFlags.MemoryRead | SectionFlags.MemoryExecute, + _symbol.Characteristics); + } + + [Fact] + public void Name() + { + Assert.Equal(".text$mn", _symbol.Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/CompileSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/CompileSymbolTest.cs new file mode 100644 index 000000000..c1535323b --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/CompileSymbolTest.cs @@ -0,0 +1,85 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class CompileSymbolTest : IClassFixture +{ + private readonly Compile2Symbol _compile2Symbol; + private readonly Compile3Symbol _compile3Symbol; + + public CompileSymbolTest(MockPdbFixture fixture) + { + var kernel32Module = fixture.SimplePdb.Modules.First(m => m.Name == "KERNEL32.dll"); + var dllmainModule = fixture.SimplePdb.Modules.First(m => + m.Name == @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\Release\dllmain.obj"); + + _compile2Symbol = kernel32Module.Symbols.OfType().First(); + _compile3Symbol = dllmainModule.Symbols.OfType().First(); + } + + [Fact] + public void Flags2() + { + Assert.Equal(SourceLanguage.Link, _compile2Symbol.Language); + Assert.Equal(CompileAttributes.None, _compile2Symbol.Attributes); + } + + [Fact] + public void Flags3() + { + Assert.Equal(SourceLanguage.Cpp, _compile3Symbol.Language); + Assert.Equal( + CompileAttributes.Sdl | CompileAttributes.SecurityChecks | CompileAttributes.Ltcg, + _compile3Symbol.Attributes); + } + + [Fact] + public void Machine2() + { + Assert.Equal(CpuType.Intel80386, _compile2Symbol.Machine); + } + + [Fact] + public void Machine3() + { + Assert.Equal(CpuType.Pentium3, _compile3Symbol.Machine); + } + + [Fact] + public void NumericVersions2() + { + Assert.Equal(0, _compile2Symbol.FrontEndMajorVersion); + Assert.Equal(0, _compile2Symbol.FrontEndMinorVersion); + Assert.Equal(0, _compile2Symbol.FrontEndBuildVersion); + Assert.Equal(14, _compile2Symbol.BackEndMajorVersion); + Assert.Equal(20, _compile2Symbol.BackEndMinorVersion); + Assert.Equal(27412, _compile2Symbol.BackEndBuildVersion); + } + + [Fact] + public void NumericVersions3() + { + Assert.Equal(19, _compile3Symbol.FrontEndMajorVersion); + Assert.Equal(29, _compile3Symbol.FrontEndMinorVersion); + Assert.Equal(30143, _compile3Symbol.FrontEndBuildVersion); + Assert.Equal(0, _compile3Symbol.FrontEndQfeVersion); + Assert.Equal(19, _compile3Symbol.BackEndMajorVersion); + Assert.Equal(29, _compile3Symbol.BackEndMinorVersion); + Assert.Equal(30143, _compile3Symbol.BackEndBuildVersion); + Assert.Equal(0, _compile3Symbol.BackEndQfeVersion); + } + + [Fact] + public void CompilerVersionString2() + { + Assert.Equal("Microsoft (R) LINK", _compile2Symbol.CompilerVersion); + } + + [Fact] + public void CompilerVersionString3() + { + Assert.Equal("Microsoft (R) Optimizing Compiler", _compile3Symbol.CompilerVersion); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/DataSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/DataSymbolTest.cs new file mode 100644 index 000000000..f98012226 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/DataSymbolTest.cs @@ -0,0 +1,57 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class DataSymbolTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public DataSymbolTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Global() + { + var symbol = _fixture.SimplePdb.Symbols.OfType().First(); + + Assert.True(symbol.IsGlobal); + Assert.Equal(CodeViewSymbolType.GData32, symbol.CodeViewSymbolType); + } + + [Fact] + public void Local() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("gs_report")) + .Symbols.OfType() + .First(); + + Assert.True(symbol.IsLocal); + Assert.Equal(CodeViewSymbolType.LData32, symbol.CodeViewSymbolType); + } + + [Fact] + public void BasicProperties() + { + var symbol = _fixture.SimplePdb.Symbols.OfType().First(); + + Assert.Equal(3, symbol.SegmentIndex); + Assert.Equal(0x38Cu, symbol.Offset); + Assert.Equal(SimpleTypeKind.Int32, Assert.IsAssignableFrom(symbol.VariableType).Kind); + } + + [Fact] + public void Name() + { + var symbol = _fixture.SimplePdb.Symbols.OfType().First(); + + Assert.Equal( + "__@@_PchSym_@00@UfhvihUzwnrmUhlfixvUivklhUzhnivhloeviUgvhgUgvhgyrmzirvhUmzgrevUhrnkovwooUivovzhvUkxsOlyq@4B2008FD98C1DD4", + symbol.Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/EnvironmentBlockTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/EnvironmentBlockTest.cs new file mode 100644 index 000000000..09187415f --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/EnvironmentBlockTest.cs @@ -0,0 +1,34 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class EnvironmentBlockTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public EnvironmentBlockTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Entries() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name == "* Linker *") + .Symbols.OfType() + .First(); + + Assert.Equal(new[] + { + ("cwd", @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll"), + ("exe", + @"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.29.30133\bin\HostX86\x86\link.exe"), + ("pdb", @"C:\Users\Admin\source\repos\AsmResolver\Release\SimpleDll.pdb"), + ("cmd", + " /ERRORREPORT:PROMPT /OUT:C:\\Users\\Admin\\source\\repos\\AsmResolver\\Release\\SimpleDll.dll /INCREMENTAL:NO /NOLOGO /MANIFEST /MANIFESTUAC:NO /manifest:embed /DEBUG /PDB:C:\\Users\\Admin\\source\\repos\\AsmResolver\\Release\\SimpleDll.pdb /SUBSYSTEM:WINDOWS /OPT:REF /OPT:ICF /LTCG:incremental /LTCGOUT:Release\\SimpleDll.iobj /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:C:\\Users\\Admin\\source\\repos\\AsmResolver\\Release\\SimpleDll.lib /MACHINE:X86 /SAFESEH /DLL"), + }, symbol.Entries.Select(x => (x.Key.Value, x.Value.Value))); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/FileStaticSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/FileStaticSymbolTest.cs new file mode 100644 index 000000000..3619f7ea6 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/FileStaticSymbolTest.cs @@ -0,0 +1,32 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class FileStaticSymbolTest : IClassFixture +{ + private readonly FileStaticSymbol _symbol; + + public FileStaticSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"msvcrt.nativeproj_110336922\objr\x86\dll_dllmain.obj")) + .Symbols.OfType().ElementAt(2) + .Symbols.OfType() + .First(); + } + + [Fact] + public void Name() + { + Assert.Equal("__proc_attached", _symbol.Name); + } + + [Fact] + public void Type() + { + Assert.Equal(SimpleTypeKind.Int32, Assert.IsAssignableFrom(_symbol.VariableType).Kind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/FrameCookieSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/FrameCookieSymbolTest.cs new file mode 100644 index 000000000..bb6b6e3c8 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/FrameCookieSymbolTest.cs @@ -0,0 +1,27 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class FrameCookieSymbolTest : IClassFixture +{ + private readonly FrameCookieSymbol _symbol; + + public FrameCookieSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"msvcrt.nativeproj_110336922\objr\x86\dll_dllmain.obj")) + .Symbols.OfType().ElementAt(1) + .Symbols.OfType() + .First(); + } + + [Fact] + public void BasicProperties() + { + Assert.Equal(-48, _symbol.FrameOffset); + Assert.Equal(FrameCookieType.Copy, _symbol.CookieType); + Assert.Equal(0, _symbol.Attributes); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/FramePointerRangeSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/FramePointerRangeSymbolTest.cs new file mode 100644 index 000000000..68ff12784 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/FramePointerRangeSymbolTest.cs @@ -0,0 +1,53 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class FramePointerRangeSymbolTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + private readonly FramePointerRangeSymbol _symbol; + + public FramePointerRangeSymbolTest(MockPdbFixture fixture) + { + _fixture = fixture; + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("dllmain.obj")) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + } + + [Fact] + public void Offset() + { + Assert.Equal(8, _symbol.Offset); + } + + [Fact] + public void FullScope() + { + Assert.True(_symbol.IsFullScope); + Assert.Equal(default, _symbol.Range); + Assert.Empty(_symbol.Gaps); + } + + [Fact] + public void NonFullScope() + { + string path = @"D:\a\_work\1\s\Intermediate\vctools\msvcrt.nativeproj_110336922\objr\x86\cpu_disp.obj"; + + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name == path) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + + Assert.False(symbol.IsFullScope); + Assert.Equal(-12, symbol.Offset); + Assert.Equal(0xa95u, symbol.Range.Start); + Assert.Equal(0xc18u, symbol.Range.End); + Assert.Empty(symbol.Gaps); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/FrameProcedureSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/FrameProcedureSymbolTest.cs new file mode 100644 index 000000000..198f0dc5c --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/FrameProcedureSymbolTest.cs @@ -0,0 +1,32 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class FrameProcedureSymbolTest : IClassFixture +{ + private readonly FrameProcedureSymbol _symbol; + + public FrameProcedureSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("cpu_disp.obj")) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + } + + [Fact] + public void BasicProperties() + { + Assert.Equal(0x24u, _symbol.FrameSize); + Assert.Equal(0u, _symbol.PaddingSize); + Assert.Equal(0, _symbol.PaddingOffset); + Assert.Equal(0xCu, _symbol.CalleeSavesSize); + Assert.Equal(0, _symbol.ExceptionHandlerOffset); + Assert.Equal(0, _symbol.ExceptionHandlerSection); + Assert.False((_symbol.Attributes & FrameProcedureAttributes.ValidCounts) != 0); + Assert.True((_symbol.Attributes & FrameProcedureAttributes.GuardCF) != 0); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/FunctionListSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/FunctionListSymbolTest.cs new file mode 100644 index 000000000..cc09e9079 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/FunctionListSymbolTest.cs @@ -0,0 +1,29 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class FunctionListSymbolTest : IClassFixture +{ + private readonly FunctionListSymbol _symbol; + + public FunctionListSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("chandler4gs.obj")) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + } + + [Fact] + public void Entries() + { + Assert.Equal(new[] + { + ("_filter_x86_sse2_floating_point_exception", 0), + ("_except_handler4_common", 0) + }, _symbol.Entries.Select(x => (x.Function!.Name!.Value, x.Count))); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/InlineSiteSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/InlineSiteSymbolTest.cs new file mode 100644 index 000000000..9f8c724e5 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/InlineSiteSymbolTest.cs @@ -0,0 +1,34 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class InlineSiteSymbolTest : IClassFixture +{ + private readonly InlineSiteSymbol _symbol; + + public InlineSiteSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"msvcrt.nativeproj_110336922\objr\x86\dll_dllmain.obj")) + .Symbols.OfType().ElementAt(3) + .Symbols.OfType().First(); + } + + [Fact] + public void Inlinee() + { + Assert.NotNull(_symbol.Inlinee); + Assert.Equal("dllmain_crt_dispatch", _symbol.Inlinee.Name); + } + + [Fact] + public void BinaryAnnotations() + { + Assert.Equal(new[] + { + BinaryAnnotationOpCode.ChangeCodeLengthAndCodeOffset + }, _symbol.Annotations.Select(x => x.OpCode)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/LabelSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/LabelSymbolTest.cs new file mode 100644 index 000000000..3417e9a55 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/LabelSymbolTest.cs @@ -0,0 +1,31 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class LabelSymbolTest : IClassFixture +{ + private readonly LabelSymbol _symbol; + + public LabelSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"msvcrt.nativeproj_110336922\objr\x86\secchk.obj")) + .Symbols.OfType().First() + .Symbols.OfType().First(); + } + + [Fact] + public void BasicProperties() + { + Assert.Equal(1, _symbol.SegmentIndex); + Assert.Equal(0x11u, _symbol.Offset); + } + + [Fact] + public void Name() + { + Assert.Equal("failure", _symbol.Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/LocalSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/LocalSymbolTest.cs new file mode 100644 index 000000000..f20d7a039 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/LocalSymbolTest.cs @@ -0,0 +1,32 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class LocalSymbolTest : IClassFixture +{ + private readonly LocalSymbol _symbol; + + public LocalSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("dllmain.obj")) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + } + + [Fact] + public void Name() + { + Assert.Equal("hModule", _symbol.Name); + } + + [Fact] + public void Type() + { + Assert.IsAssignableFrom(_symbol.VariableType); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/ProcedureSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/ProcedureSymbolTest.cs new file mode 100644 index 000000000..d6cb43da7 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/ProcedureSymbolTest.cs @@ -0,0 +1,121 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class ProcedureSymbolTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ProcedureSymbolTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Global() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"SimpleDll\Release\dllmain.obj")) + .Symbols.OfType() + .First(); + + Assert.True(symbol.IsGlobal); + Assert.Equal(CodeViewSymbolType.GProc32, symbol.CodeViewSymbolType); + } + + [Fact] + public void Local() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"gs_support.obj")) + .Symbols.OfType() + .First(); + + Assert.True(symbol.IsLocal); + Assert.Equal(CodeViewSymbolType.LProc32, symbol.CodeViewSymbolType); + } + + [Fact] + public void Name() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"SimpleDll\Release\dllmain.obj")) + .Symbols.OfType() + .First(); + + Assert.Equal("DllMain", symbol.Name); + } + + [Fact] + public void Size() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"SimpleDll\Release\dllmain.obj")) + .Symbols.OfType() + .First(); + + Assert.Equal(8u, symbol.Size); + } + + [Fact] + public void DebugOffsets() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"SimpleDll\Release\dllmain.obj")) + .Symbols.OfType() + .First(); + + Assert.Equal(0u, symbol.DebugStartOffset); + Assert.Equal(5u, symbol.DebugEndOffset); + } + + [Fact] + public void Attributes() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"SimpleDll\Release\dllmain.obj")) + .Symbols.OfType() + .First(); + + Assert.Equal(ProcedureAttributes.OptimizedDebugInfo, symbol.Attributes); + } + + [Fact] + public void ProcedureType() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"SimpleDll\Release\dllmain.obj")) + .Symbols.OfType() + .First(); + + Assert.NotNull(symbol.ProcedureType); + } + + [Fact] + public void Children() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"SimpleDll\Release\dllmain.obj")) + .Symbols.OfType() + .First(); + + Assert.Equal(new[] + { + CodeViewSymbolType.Local, + CodeViewSymbolType.DefRangeRegisterRel, + CodeViewSymbolType.DefRangeFramePointerRelFullScope, + CodeViewSymbolType.Local, + CodeViewSymbolType.DefRangeRegisterRel, + CodeViewSymbolType.DefRangeFramePointerRelFullScope, + CodeViewSymbolType.Local, + CodeViewSymbolType.DefRangeRegisterRel, + CodeViewSymbolType.DefRangeFramePointerRelFullScope, + CodeViewSymbolType.FrameProc, + CodeViewSymbolType.RegRel32, + CodeViewSymbolType.RegRel32, + CodeViewSymbolType.RegRel32, + }, symbol.Symbols.Select(s => s.CodeViewSymbolType)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRangeSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRangeSymbolTest.cs new file mode 100644 index 000000000..2a59b2bfd --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRangeSymbolTest.cs @@ -0,0 +1,32 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class RegisterRangeSymbolTest : IClassFixture +{ + private readonly RegisterRangeSymbol _symbol; + + public RegisterRangeSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("gs_support.obj")) + .Symbols.OfType().First() + .Symbols.OfType().First(); + } + + [Fact] + public void BasicProperties() + { + Assert.Equal(0x0001, _symbol.Range.SectionStart); + Assert.Equal(0x000004E1u, _symbol.Range.Start); + Assert.Equal(0x000004E8 - 0x000004E1u, _symbol.Range.Length); + } + + [Fact] + public void EmptyGaps() + { + Assert.Empty(_symbol.Gaps); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRelativeRangeSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRelativeRangeSymbolTest.cs new file mode 100644 index 000000000..5b040b83c --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRelativeRangeSymbolTest.cs @@ -0,0 +1,34 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class RegisterRelativeRangeSymbolTest : IClassFixture +{ + private readonly RegisterRelativeRangeSymbol _symbol; + + public RegisterRelativeRangeSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("dllmain.obj")) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + } + + [Fact] + public void BasicProperties() + { + Assert.Equal(4, _symbol.Offset); + Assert.Equal(0u, _symbol.Range.Start); + Assert.Equal(1u, _symbol.Range.SectionStart); + Assert.Equal(1u, _symbol.Range.Length); + } + + [Fact] + public void NoGaps() + { + Assert.Empty(_symbol.Gaps); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRelativeSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRelativeSymbolTest.cs new file mode 100644 index 000000000..ddc32d4b2 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterRelativeSymbolTest.cs @@ -0,0 +1,39 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class RegisterRelativeSymbolTest : IClassFixture +{ + private readonly RegisterRelativeSymbol _symbol; + + public RegisterRelativeSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("dllmain.obj")) + .Symbols.OfType().First() + .Symbols.OfType() + .First(); + } + + [Fact] + public void Offset() + { + Assert.Equal(8, _symbol.Offset); + } + + [Fact] + public void Type() + { + Assert.IsAssignableFrom(_symbol.VariableType); + } + + [Fact] + public void Name() + { + Assert.Equal("hModule", _symbol.Name); + } + +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterSymbolTest.cs new file mode 100644 index 000000000..b236ec644 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/RegisterSymbolTest.cs @@ -0,0 +1,31 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class RegisterSymbolTest : IClassFixture +{ + private readonly RegisterSymbol _symbol; + + public RegisterSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name!.Contains(@"msvcrt.nativeproj_110336922\objr\x86\secchk.obj")) + .Symbols.OfType().First() + .Symbols.OfType().First(); + } + + [Fact] + public void Name() + { + Assert.Equal("cookie", _symbol.Name); + } + + [Fact] + public void Type() + { + Assert.Equal(SimpleTypeKind.UInt32, Assert.IsAssignableFrom(_symbol.VariableType).Kind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/SectionSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/SectionSymbolTest.cs new file mode 100644 index 000000000..8f45f08b0 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/SectionSymbolTest.cs @@ -0,0 +1,36 @@ +using System.Linq; +using AsmResolver.PE.File.Headers; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class SectionSymbolTest : IClassFixture +{ + private readonly SectionSymbol _symbol; + + public SectionSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name == "* Linker *") + .Symbols.OfType().First(); + } + + [Fact] + public void BasicProperties() + { + Assert.Equal(1, _symbol.SectionNumber); + Assert.Equal(0x1000u, _symbol.Rva); + Assert.Equal(0xCE8u, _symbol.Size); + Assert.Equal(0x1000u, _symbol.Alignment); + Assert.Equal( + SectionFlags.MemoryRead | SectionFlags.MemoryExecute | SectionFlags.ContentCode, + _symbol.Attributes); + } + + [Fact] + public void Name() + { + Assert.Equal(".text", _symbol.Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/ThunkSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/ThunkSymbolTest.cs new file mode 100644 index 000000000..e42bb876a --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/ThunkSymbolTest.cs @@ -0,0 +1,38 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class ThunkSymbolTest : IClassFixture +{ + private readonly ThunkSymbol _symbol; + + public ThunkSymbolTest(MockPdbFixture fixture) + { + _symbol = fixture.SimplePdb + .Modules.First(m => m.Name == "Import:VCRUNTIME140.dll") + .Symbols.OfType() + .First(); + } + + [Fact] + public void BasicProperties() + { + Assert.Equal(1, _symbol.SegmentIndex); + Assert.Equal(0xC28u, _symbol.Offset); + Assert.Equal(6u, _symbol.Size); + } + + [Fact] + public void Name() + { + Assert.Equal("__std_type_info_destroy_list", _symbol.Name); + } + + [Fact] + public void EmptyChildren() + { + Assert.Empty(_symbol.Symbols); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/UsingNamespaceTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/UsingNamespaceTest.cs new file mode 100644 index 000000000..42200e282 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/UsingNamespaceTest.cs @@ -0,0 +1,26 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class UsingNamespaceTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public UsingNamespaceTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + var symbol = _fixture.SimplePdb + .Modules.First(m => m.Name!.Contains("argv_mode.obj")) + .Symbols.OfType() + .First(); + + Assert.Equal("std", symbol.Name); + } +} diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index b3874df72..b31d8874c 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,7 +8,7 @@ - + all diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericDerivedType.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericDerivedType.cs new file mode 100644 index 000000000..91d91cc10 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericDerivedType.cs @@ -0,0 +1,6 @@ +namespace AsmResolver.DotNet.TestCases.Generics +{ + public class GenericDerivedType : GenericType + { + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericInterfaceImplementation.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericInterfaceImplementation.cs new file mode 100644 index 000000000..7cbee2dd5 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericInterfaceImplementation.cs @@ -0,0 +1,6 @@ +namespace AsmResolver.DotNet.TestCases.Generics +{ + public class GenericInterfaceImplementation : IGenericInterface + { + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/IGenericInterface.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/IGenericInterface.cs new file mode 100644 index 000000000..6ad7d0b37 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/IGenericInterface.cs @@ -0,0 +1,7 @@ +namespace AsmResolver.DotNet.TestCases.Generics +{ + public interface IGenericInterface + { + + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/GenericInstanceMethods.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/GenericInstanceMethods.cs new file mode 100644 index 000000000..d48a2bb4f --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/GenericInstanceMethods.cs @@ -0,0 +1,9 @@ +namespace AsmResolver.DotNet.TestCases.Methods +{ + public class GenericInstanceMethods + { + public void InstanceParameterlessMethod() + { + } + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int32Enum.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int32Enum.cs new file mode 100644 index 000000000..c405e0c9e --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int32Enum.cs @@ -0,0 +1,9 @@ +namespace AsmResolver.DotNet.TestCases.Types +{ + public enum Int32Enum : int + { + Member1 = 1, + Member2 = 2, + Member3 = 3, + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int64Enum.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int64Enum.cs new file mode 100644 index 000000000..0b129abf3 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int64Enum.cs @@ -0,0 +1,9 @@ +namespace AsmResolver.DotNet.TestCases.Types +{ + public enum Int64Enum : long + { + Member1 = 1, + Member2 = 2, + Member3 = 3, + } +} diff --git a/test/TestBinaries/Native/CallManagedExport/CallManagedExport.vcxproj b/test/TestBinaries/Native/CallManagedExport/CallManagedExport.vcxproj index 6b4372aff..5627bd5d9 100644 --- a/test/TestBinaries/Native/CallManagedExport/CallManagedExport.vcxproj +++ b/test/TestBinaries/Native/CallManagedExport/CallManagedExport.vcxproj @@ -72,19 +72,19 @@ true - $(ProjectDir)\bin\$(Configuration)\$(Platform) + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ false - $(ProjectDir)\bin\$(Configuration)\$(Platform) + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ true - $(ProjectDir)\bin\$(Configuration)\$(Platform) + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ false - $(ProjectDir)\bin\$(Configuration)\$(Platform) + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ @@ -151,4 +151,4 @@ - \ No newline at end of file + diff --git a/test/TestBinaries/Native/SimpleDll/SimpleDll.vcxproj b/test/TestBinaries/Native/SimpleDll/SimpleDll.vcxproj index a7c809fc8..456045643 100644 --- a/test/TestBinaries/Native/SimpleDll/SimpleDll.vcxproj +++ b/test/TestBinaries/Native/SimpleDll/SimpleDll.vcxproj @@ -72,16 +72,19 @@ true - $(ProjectDir)\bin\$(Configuration)\ + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ true + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ false + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ false + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ @@ -174,4 +177,4 @@ - \ No newline at end of file + diff --git a/test/TestBinaries/Native/TlsTest/TlsTest.vcxproj b/test/TestBinaries/Native/TlsTest/TlsTest.vcxproj index deb3b36f6..c693efb49 100644 --- a/test/TestBinaries/Native/TlsTest/TlsTest.vcxproj +++ b/test/TestBinaries/Native/TlsTest/TlsTest.vcxproj @@ -72,19 +72,19 @@ true - $(ProjectDir)\bin\$(Configuration)\$(Platform) + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ true - $(ProjectDir)\bin\$(Configuration)\$(Platform) + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ false - $(ProjectDir)\bin\$(Configuration)\$(Platform) + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ false - $(ProjectDir)\bin\$(Configuration)\$(Platform) + $(ProjectDir)\bin\$(Configuration)\$(Platform)\ @@ -159,4 +159,4 @@ - \ No newline at end of file +