diff --git a/AsmResolver.sln b/AsmResolver.sln
index 90430c976..0cf5601e0 100644
--- a/AsmResolver.sln
+++ b/AsmResolver.sln
@@ -83,12 +83,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
LICENSE.md = LICENSE.md
README.md = README.md
Directory.Build.props = Directory.Build.props
+ appveyor.yml = appveyor.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.Pdb", "src\AsmResolver.Symbols.Pdb\AsmResolver.Symbols.Pdb.csproj", "{9E311832-D0F2-42CA-84DD-9A91B88F0287}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.Pdb.Tests", "test\AsmResolver.Symbols.Pdb.Tests\AsmResolver.Symbols.Pdb.Tests.csproj", "{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.DotNet.Dynamic.Tests", "test\AsmResolver.DotNet.Dynamic.Tests\AsmResolver.DotNet.Dynamic.Tests.csproj", "{C089D0AB-B428-4136-89CC-7974CB590513}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsmResolver.DotNet.Dynamic", "src\AsmResolver.DotNet.Dynamic\AsmResolver.DotNet.Dynamic.csproj", "{62420213-67AD-40FC-B451-BD05C2437CE3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -451,6 +456,30 @@ Global
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.Build.0 = Release|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.ActiveCfg = Release|Any CPU
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.Build.0 = Release|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x64.Build.0 = Debug|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x86.Build.0 = Debug|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x64.ActiveCfg = Release|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x64.Build.0 = Release|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x86.ActiveCfg = Release|Any CPU
+ {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x86.Build.0 = Release|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x64.Build.0 = Debug|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x86.Build.0 = Debug|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x64.ActiveCfg = Release|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x64.Build.0 = Release|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x86.ActiveCfg = Release|Any CPU
+ {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -490,6 +519,8 @@ Global
{2D1DF5DA-7367-4490-B3F0-B996348E150B} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0}
{9E311832-D0F2-42CA-84DD-9A91B88F0287} = {34A95168-A162-4F6A-803B-B6F221FE9EA6}
{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE} = {786C1732-8C96-45DD-97BB-639C9AA7F45B}
+ {C089D0AB-B428-4136-89CC-7974CB590513} = {786C1732-8C96-45DD-97BB-639C9AA7F45B}
+ {62420213-67AD-40FC-B451-BD05C2437CE3} = {34A95168-A162-4F6A-803B-B6F221FE9EA6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3302AC79-6D23-4E7D-8C5F-C0C7261044D0}
diff --git a/README.md b/README.md
index 31582ebfe..9bf2ec12f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-AsmResolver
-===========
+# AsmResolver
[![Master branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/master.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/master)
[![Nuget feed](https://img.shields.io/nuget/v/AsmResolver.svg)](https://www.nuget.org/packages/AsmResolver/)
@@ -12,8 +11,44 @@ AsmResolver is a PE inspection library allowing .NET programmers to read, modify
AsmResolver is released under the MIT license.
-Binaries
---------
+## Features
+
+AsmResolver has a lot of features. Below a non-exhaustive list:
+
+- [x] Create, read and write PE files
+ - [x] Inspect and update PE headers.
+ - [x] Create, read and write sections.
+- [x] Create, read and write various data directories
+ - [x] Debug Directory (CodeView)
+ - [x] .NET Directory
+ - [x] CIL assembler and disassemblers
+ - [x] Metadata Directory (tables, strings, user-strings, blobs, GUIDs)
+ - [x] Resources Directory
+ - [x] Strong Name Signing
+ - [x] VTable Fixup Directory
+ - [x] Exception Directory (AMD64)
+ - [x] Export Directory
+ - [x] Import Directory
+ - [x] Base Relocation Directory
+ - [x] TLS Directory
+ - [x] Win32 Resources Directory
+- [x] Fully mutable object model for .NET modules that is similar to System.Reflection
+ - [x] Strong type-system with many useful factory methods for quickly constructing new metadata.
+ - [x] Full metadata importing and cloning (useful for injecting metadata into another assembly)
+ - [x] .NET Framework 2.0+, .NET Core and .NET 5+ binary file support.
+ - [x] Infer memory layout of types statically.
+ - [x] Create, read and write managed resource sets (`.resources` files)
+ - [x] Create new method bodies containing native code.
+ - [x] Highly configurable reader and writer options and custom error handling for both.
+ - [x] Rich support for AppHost and SingleFileHost bundled files.
+
+
+## Documentation
+
+Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library.
+
+
+## Binaries
Stable builds:
@@ -30,13 +65,7 @@ Nightly builds:
| development | [![Development branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/development.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/development)
-Documentation
--------------
-Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library.
-
-
-Compiling
----------
+## Compiling
The solution can be build using the .NET SDK or an IDE that works with the .NET SDK (such as Visual Studio and JetBrains Rider). The main packages target .NET Standard 2.0, and the xUnit test projects target .NET Core 3.1.
@@ -51,20 +80,18 @@ To run all tests, simply run:
$ dotnet test
```
+## Contributing
-Contributing
-------------
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on general workflow and code style.
-Found a bug or have questions?
-------------------------------
+## Found a bug or have questions?
+
Please use the [issue tracker](https://github.com/Washi1337/AsmResolver/issues). Try to be as descriptive as possible.
You can also join the [Discord](https://discord.gg/Y7DTBkbhJJ) to engage more directly with the community.
-Acknowledgements
-----------------
+## Acknowledgements
AsmResolver started out as a hobby project, but has grown into a community project with various contributors. Without these people, AsmResolver would not have been where it is today!
@@ -73,4 +100,3 @@ AsmResolver started out as a hobby project, but has grown into a community proje
- Another big thank you to all the people that suggested new features, provided feedback on the API design, have done extensive testing, and/or reported bugs on the [issue board](https://github.com/Washi1337/AsmResolver/issues), by e-mail, or through DMs.
If you feel you have been under-represented in these acknowledgements, feel free to contact me.
-
diff --git a/appveyor.yml b/appveyor.yml
index a350fd134..6c4e999bc 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -4,7 +4,7 @@
- master
image: Visual Studio 2022
- version: 4.11.0-master-build.{build}
+ version: 4.11.2-master-build.{build}
configuration: Release
skip_commits:
@@ -23,7 +23,7 @@
deploy:
provider: NuGet
api_key:
- secure: DCeHUu0aAsOjRnoi2DpcuXpj0apD7dxHzglSamP7LGzcZjhIvTBi1ONnjIa7L2zm
+ secure: L3fXsS7umzD8zwAvTsdGxOg/E6tQ4IR4MfwBAcO8elE7ZwjZ8HO8UPwjiWbp4RMw
skip_symbols: false
artifact: /.*\.nupkg/
diff --git a/docs/peimage/tls.rst b/docs/peimage/tls.rst
index 564b25600..ad2f97ac0 100644
--- a/docs/peimage/tls.rst
+++ b/docs/peimage/tls.rst
@@ -6,7 +6,7 @@ Executables that use multiple threads might require static (non-stack) memory th
All code relevant to the TLS data directory of a PE resides in the following namespace:
.. code-block:: csharp
-
+
using AsmResolver.PE.Tls;
@@ -21,7 +21,7 @@ The PE file format defines a segment of memory within the TLS data directory tha
.. code-block:: csharp
-
+
var indexSegment = new DataSegment(new byte[8]);
var directory = new TlsDirectory
@@ -48,18 +48,14 @@ Next to static initialization data, it is also possible to specify a list of fun
Creating new TLS directories
----------------------------
-Since the TLS data directory stores its data using virtual addresses (VA) rather than relative virtual addresses (RVA), AsmResolver requires the image base as well as the pointer size. This is done through the ``ImageBase`` and ``Is32Bit`` properties. By default, the following values are assumed:
+Adding a new TLS directory to an image can be done using the parameterless constructor of the ``TlsDirectory`` class:
.. code-block:: csharp
- var directory = new TlsDirectory();
- directory.ImageBase = 0x00400000;
- directory.Is32Bit = true;
-
-
-Typically, you should make sure they are in sync with the values found in the file and optional header of the final PE file. Upon reading from an existing PE file, these two properties are initialized to the values stored in these two headers.
+ var tlsDirectory = new TlsDirectory();
+ image.TlsDirectory = tlsDirectory;
-When building a relocatable PE file, you might also need to add base address relocations to the VAs inside the TLS directory. To quickly get all the base relocations required, use the ``GetRequiredBaseRelocations`` method:
+A TLS directory references all its sub segments using virtual addresses (VA) rather than relative addresses (RVA). This means that constructing a relocatable PE image with a TLS directory requires base relocation entries to be registered that let the Windows PE loader rebase all virtual addresses used in the directory when necessary. To quickly register all the required base relocations, you can call the ``GetRequiredBaseRelocations`` method and add all returned entries to the base relocation directory of the PE image:
.. code-block:: csharp
@@ -67,5 +63,5 @@ When building a relocatable PE file, you might also need to add base address rel
IPEImage image = ...;
- foreach (var relocation in image.TlsDirectory.GetRequiredBaseRelocations())
- image.Relocations.Add(relocation);
\ No newline at end of file
+ foreach (var relocation in tlsDirectory.GetRequiredBaseRelocations())
+ image.Relocations.Add(relocation);
diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj
new file mode 100644
index 000000000..1f4ff4172
--- /dev/null
+++ b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj
@@ -0,0 +1,32 @@
+
+
+
+ AsmResolver.DotNet.Dynamic
+ Dynamic method support for the AsmResolver executable file inspection toolsuite.
+ exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly dynamic
+ true
+ 1701;1702;NU5105
+ net6.0;netcoreapp3.1;netstandard2.0
+ enable
+
+
+
+ bin\Debug\AsmResolver.DotNet.xml
+
+
+
+ bin\Release\AsmResolver.DotNet.xml
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs
similarity index 92%
rename from src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs
rename to src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs
index d3244f8e2..6cfa15b76 100644
--- a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs
+++ b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs
@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Reflection;
+using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Serialized;
using AsmResolver.DotNet.Signatures;
using AsmResolver.IO;
using AsmResolver.PE.DotNet.Cil;
using AsmResolver.PE.DotNet.Metadata.Tables;
-namespace AsmResolver.DotNet.Code.Cil
+namespace AsmResolver.DotNet.Dynamic
{
///
/// Provides an implementation of that resolves operands based on
@@ -34,13 +35,13 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe
switch (token.Table)
{
case TableIndex.TypeDef:
- object? type = _tokens[(int) token.Rid];
+ object? type = _tokens[(int)token.Rid];
if (type is RuntimeTypeHandle runtimeTypeHandle)
return _importer.ImportType(Type.GetTypeFromHandle(runtimeTypeHandle));
break;
case TableIndex.Field:
- object? field = _tokens[(int) token.Rid];
+ object? field = _tokens[(int)token.Rid];
if (field is null)
return null;
@@ -61,7 +62,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe
case TableIndex.Method:
case TableIndex.MemberRef:
- object? obj = _tokens[(int) token.Rid];
+ object? obj = _tokens[(int)token.Rid];
if (obj is null)
return null;
@@ -94,7 +95,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe
break;
case TableIndex.StandAloneSig:
- var reader = ByteArrayDataSource.CreateReader((byte[]) _tokens[(int) token.Rid]!);
+ var reader = ByteArrayDataSource.CreateReader((byte[])_tokens[(int)token.Rid]!);
return CallingConventionSignature.FromReader(new BlobReadContext(_readerContext), ref reader);
}
@@ -104,7 +105,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe
///
public override object? ResolveString(MetadataToken token)
{
- return _tokens[(int) token.Rid] as string;
+ return _tokens[(int)token.Rid] as string;
}
}
}
diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs
new file mode 100644
index 000000000..4349d9006
--- /dev/null
+++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using AsmResolver.DotNet.Code.Cil;
+using AsmResolver.DotNet.Serialized;
+using AsmResolver.DotNet.Signatures;
+using AsmResolver.DotNet.Signatures.Types;
+using AsmResolver.IO;
+using AsmResolver.PE.DotNet.Cil;
+using AsmResolver.PE.DotNet.Metadata.Tables;
+using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes;
+
+namespace AsmResolver.DotNet.Dynamic
+{
+ ///
+ /// Represents a single method in a type definition of a .NET module.
+ ///
+ public class DynamicMethodDefinition : MethodDefinition
+ {
+ ///
+ /// Create a Dynamic Method Definition
+ ///
+ /// Target Module
+ /// Dynamic Method / Delegate / DynamicResolver
+ public DynamicMethodDefinition(ModuleDefinition module, object dynamicMethodObj) :
+ base(new MetadataToken(TableIndex.Method, 0))
+ {
+ dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj);
+ var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method");
+ if (methodBase is null)
+ {
+ throw new ArgumentException(
+ "Could not get the underlying method base in the provided dynamic method object.");
+ }
+
+ Module = module;
+ Name = methodBase.Name;
+ Attributes = (MethodAttributes)methodBase.Attributes;
+ Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase, module));
+ CilMethodBody = CreateDynamicMethodBody(this, dynamicMethodObj);
+ }
+
+ private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition module)
+ {
+ var imp = new ReferenceImporter(module);
+ var returnType = methodBase is MethodInfo info
+ ? imp.ImportTypeSignature(info.ReturnType)
+ : module.CorLibTypeFactory.Void;
+
+ var parameters = methodBase.GetParameters();
+
+ var parameterTypes = new TypeSignature[parameters.Length];
+ for (int i = 0; i < parameterTypes.Length; i++)
+ parameterTypes[i] = imp.ImportTypeSignature(parameters[i].ParameterType);
+
+ return new MethodSignature(
+ methodBase.IsStatic ? 0 : CallingConventionAttributes.HasThis,
+ returnType, parameterTypes);
+ }
+
+ ///
+ public override ModuleDefinition Module { get; }
+
+ ///
+ /// Creates a CIL method body from a dynamic method.
+ ///
+ /// The method that owns the method body.
+ /// The Dynamic Method/Delegate/DynamicResolver.
+ /// The method body.
+ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, object dynamicMethodObj)
+ {
+ if (!(method.Module is SerializedModuleDefinition module))
+ throw new ArgumentException("Method body should reference a serialized module.");
+
+ var result = new CilMethodBody(method);
+ dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj);
+
+ //Get Runtime Fields
+ byte[] code = FieldReader.ReadField(dynamicMethodObj, "m_code")!;
+ object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!;
+ var tokenList = FieldReader.ReadField>(scope, "m_tokens")!;
+ byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!;
+ byte[] ehHeader = FieldReader.ReadField(dynamicMethodObj, "m_exceptionHeader")!;
+ var ehInfos = FieldReader.ReadField>(dynamicMethodObj, "m_exceptions")!;
+
+ //Local Variables
+ DynamicMethodHelper.ReadLocalVariables(result, method, localSig);
+
+ // Read raw instructions.
+ var reader = ByteArrayDataSource.CreateReader(code);
+ var disassembler = new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList));
+ result.Instructions.AddRange(disassembler.ReadInstructions());
+
+ //Exception Handlers
+ DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, new ReferenceImporter(module));
+
+ return result;
+ }
+ }
+}
diff --git a/src/AsmResolver.DotNet/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs
similarity index 63%
rename from src/AsmResolver.DotNet/DynamicMethodHelper.cs
rename to src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs
index f3cece5db..118fd49ac 100644
--- a/src/AsmResolver.DotNet/DynamicMethodHelper.cs
+++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs
@@ -6,22 +6,37 @@
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Serialized;
using AsmResolver.DotNet.Signatures;
+using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.IO;
using AsmResolver.PE.DotNet.Cil;
+using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
-namespace AsmResolver.DotNet
+namespace AsmResolver.DotNet.Dynamic
{
internal static class DynamicMethodHelper
{
+ private static readonly MethodInfo GetTypeFromHandleUnsafeMethod;
+
+ static DynamicMethodHelper()
+ {
+ // We need to use reflection for this to stay compatible with .netstandard 2.0.
+ GetTypeFromHandleUnsafeMethod = typeof(Type)
+ .GetMethod("GetTypeFromHandleUnsafe",
+ (BindingFlags) (-1),
+ null,
+ new[] {typeof(IntPtr)},
+ null)!;
+ }
+
public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition method, byte[] localSig)
{
if (!(method.Module is SerializedModuleDefinition module))
throw new ArgumentException("Method body should reference a serialized module.");
var reader = ByteArrayDataSource.CreateReader(localSig);
- if (CallingConventionSignature.FromReader(
- new BlobReadContext(module.ReaderContext),
- ref reader) is not LocalVariablesSignature localsSignature)
+ if (ReadLocalVariableSignature(
+ new BlobReadContext(module.ReaderContext),
+ ref reader) is not LocalVariablesSignature localsSignature)
{
throw new ArgumentException("Invalid local variables signature.");
}
@@ -30,14 +45,58 @@ public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition
methodBody.LocalVariables.Add(new CilLocalVariable(localsSignature.VariableTypes[i]));
}
+ private static TypeSignature ReadTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader)
+ {
+ return (ElementType) reader.PeekByte() == ElementType.Internal
+ ? ReadInternalTypeSignature(context, ref reader)
+ : TypeSignature.FromReader(in context, ref reader);
+ }
+
+ private static TypeSignature ReadInternalTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader)
+ {
+ var address = IntPtr.Size switch
+ {
+ 4 => new IntPtr(reader.ReadInt32()),
+ _ => new IntPtr(reader.ReadInt64())
+ };
+
+ // Let the runtime translate the address to a type and import it.
+ var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address });
+
+ var type = clrType is not null
+ ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType)
+ : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec);
+
+ return new TypeDefOrRefSignature(type);
+ }
+
+ private static LocalVariablesSignature ReadLocalVariableSignature(
+ in BlobReadContext context,
+ ref BinaryStreamReader reader)
+ {
+ var result = new LocalVariablesSignature();
+ result.Attributes = (CallingConventionAttributes) reader.ReadByte();
+
+ if (!reader.TryReadCompressedUInt32(out uint count))
+ {
+ context.ReaderContext.BadImage("Invalid number of local variables in local variable signature.");
+ return result;
+ }
+
+ for (int i = 0; i < count; i++)
+ result.VariableTypes.Add(ReadTypeSignature(context, ref reader));
+
+ return result;
+ }
+
public static void ReadReflectionExceptionHandlers(CilMethodBody methodBody,
IList? ehInfos, byte[] ehHeader, ReferenceImporter importer)
{
//Sample needed!
- if (ehHeader is {Length: > 4})
+ if (ehHeader is { Length: > 4 })
throw new NotImplementedException("Exception handlers from ehHeader not supported yet.");
- if (ehInfos is {Count: > 0})
+ if (ehInfos is { Count: > 0 })
{
foreach (var ehInfo in ehInfos)
InterpretEHInfo(methodBody, importer, ehInfo);
@@ -61,7 +120,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter
int handlerStart = FieldReader.ReadField(ehInfo, "m_catchAddr")![i];
int handlerEnd = FieldReader.ReadField(ehInfo, "m_catchEndAddr")![i];
var exceptionType = FieldReader.ReadField(ehInfo, "m_catchClass")![i];
- var handlerType = (CilExceptionHandlerType) FieldReader.ReadField(ehInfo, "m_type")![i];
+ var handlerType = (CilExceptionHandlerType)FieldReader.ReadField(ehInfo, "m_type")![i];
var endTryLabel = instructions.GetByOffset(tryEnd)?.CreateLabel() ?? new CilOffsetLabel(tryEnd);
@@ -81,7 +140,6 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter
}
}
- [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls GetTypes")]
public static object ResolveDynamicResolver(object dynamicMethodObj)
{
//Convert dynamicMethodObj to DynamicResolver
@@ -111,7 +169,7 @@ public static object ResolveDynamicResolver(object dynamicMethodObj)
.Invoke(dynamicMethodObj, null);
//Create instance of dynamicResolver
- dynamicMethodObj = Activator.CreateInstance(dynamicResolver, (BindingFlags) (-1), null, new[]
+ dynamicMethodObj = Activator.CreateInstance(dynamicResolver, (BindingFlags)(-1), null, new[]
{
ilGenerator
}, null)!;
diff --git a/src/AsmResolver.DotNet/FieldReader.cs b/src/AsmResolver.DotNet.Dynamic/FieldReader.cs
similarity index 96%
rename from src/AsmResolver.DotNet/FieldReader.cs
rename to src/AsmResolver.DotNet.Dynamic/FieldReader.cs
index 8133f1e63..40e4a4450 100644
--- a/src/AsmResolver.DotNet/FieldReader.cs
+++ b/src/AsmResolver.DotNet.Dynamic/FieldReader.cs
@@ -1,6 +1,6 @@
-using System.Reflection;
+using System.Reflection;
-namespace AsmResolver.DotNet
+namespace AsmResolver.DotNet.Dynamic
{
internal static class FieldReader
{
diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj
index 456ce3119..8b9a38a50 100644
--- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj
+++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj
@@ -8,6 +8,7 @@
1701;1702;NU5105
enable
net6.0;netcoreapp3.1;netstandard2.0
+ true
@@ -23,11 +24,11 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs
index d6eeac3c6..30750b64a 100644
--- a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs
+++ b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs
@@ -121,6 +121,9 @@ public static MemberDiscoveryResult DiscoverMembersInModule(ModuleDefinition mod
private void CollectExistingMembers()
{
+ if (_module.DotNetDirectory?.Metadata is null)
+ return;
+
if ((_flags & MemberDiscoveryFlags.PreserveTypeOrder) != 0)
CollectMembersFromTable(TableIndex.TypeDef);
if ((_flags & MemberDiscoveryFlags.PreserveFieldOrder) != 0)
@@ -139,8 +142,8 @@ private void CollectMembersFromTable(TableIndex tableIndex)
where TMember: IMetadataMember, IModuleProvider
{
// Get original number of elements in the table.
- int count = _module.DotNetDirectory!.Metadata
- !.GetStream()
+ int count = _module.DotNetDirectory!.Metadata!
+ .GetStream()
.GetTable(tableIndex)
.Count;
diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs
index 37b5c136b..2aeee551a 100644
--- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs
+++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs
@@ -26,16 +26,16 @@ private void AddCustomAttribute(MetadataToken ownerToken, CustomAttribute attrib
table.Add(attribute, row);
}
- private uint AddResolutionScope(IResolutionScope? scope)
+ private uint AddResolutionScope(IResolutionScope? scope, bool allowDuplicates, bool preserveRid)
{
if (!AssertIsImported(scope))
return 0;
var token = scope.MetadataToken.Table switch
{
- TableIndex.AssemblyRef => GetAssemblyReferenceToken(scope as AssemblyReference),
- TableIndex.TypeRef => GetTypeReferenceToken(scope as TypeReference),
- TableIndex.ModuleRef => GetModuleReferenceToken(scope as ModuleReference),
+ TableIndex.AssemblyRef => AddAssemblyReference(scope as AssemblyReference, allowDuplicates, preserveRid),
+ TableIndex.TypeRef => AddTypeReference(scope as TypeReference, allowDuplicates, preserveRid),
+ TableIndex.ModuleRef => AddModuleReference(scope as ModuleReference, allowDuplicates, preserveRid),
TableIndex.Module => 0,
_ => throw new ArgumentOutOfRangeException(nameof(scope))
};
diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs
index 1abba1f59..b3eca662b 100644
--- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs
+++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs
@@ -10,7 +10,10 @@ public partial class DotNetDirectoryBuffer : IMetadataTokenProvider
public uint GetUserStringIndex(string value) => Metadata.UserStringsStream.GetStringIndex(value);
///
- public MetadataToken GetTypeReferenceToken(TypeReference? type) => AddTypeReference(type, false);
+ public MetadataToken GetTypeReferenceToken(TypeReference? type)
+ {
+ return AddTypeReference(type, false, false);
+ }
///
/// Adds a type reference to the buffer.
@@ -20,19 +23,25 @@ public partial class DotNetDirectoryBuffer : IMetadataTokenProvider
/// true if the row is always to be added to the end of the buffer, false if a duplicated row
/// is supposed to be removed and the token of the original should be returned instead.
///
+ ///
+ /// true if the metadata token of the type should be preserved, false otherwise.
+ ///
/// The newly assigned metadata token.
- public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates)
+ public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates, bool preserveRid)
{
if (!AssertIsImported(type))
return MetadataToken.Zero;
var table = Metadata.TablesStream.GetDistinctTable(TableIndex.TypeRef);
var row = new TypeReferenceRow(
- AddResolutionScope(type.Scope),
+ AddResolutionScope(type.Scope, allowDuplicates, preserveRid),
Metadata.StringsStream.GetStringIndex(type.Name),
Metadata.StringsStream.GetStringIndex(type.Namespace));
- var token = table.Add(row, allowDuplicates);
+ var token = preserveRid
+ ? table.Insert(type.MetadataToken.Rid, row, allowDuplicates)
+ : table.Add(row, allowDuplicates);
+
_tokenMapping.Register(type, token);
AddCustomAttributes(token, type);
return token;
@@ -164,7 +173,7 @@ public MetadataToken AddStandAloneSignature(StandAloneSignature? signature, bool
///
public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly)
{
- return AddAssemblyReference(assembly, false);
+ return AddAssemblyReference(assembly, false, false);
}
///
@@ -175,8 +184,11 @@ public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly)
/// true if the row is always to be added to the end of the buffer, false if a duplicated row
/// is supposed to be removed and the token of the original should be returned instead.
///
+ ///
+ /// true if the metadata token of the assembly should be preserved, false otherwise.
+ ///
/// The newly assigned metadata token.
- public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates)
+ public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates, bool preserveRid)
{
if (assembly is null || !AssertIsImported(assembly))
return MetadataToken.Zero;
@@ -193,7 +205,10 @@ public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allo
Metadata.StringsStream.GetStringIndex(assembly.Culture),
Metadata.BlobStream.GetBlobIndex(assembly.HashValue));
- var token = table.Add(row, allowDuplicates);
+ var token = preserveRid
+ ? table.Insert(assembly.MetadataToken.Rid, row, allowDuplicates)
+ : table.Add(row, allowDuplicates);
+
AddCustomAttributes(token, assembly);
return token;
}
@@ -205,7 +220,7 @@ public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allo
/// The new metadata token assigned to the module reference.
public MetadataToken GetModuleReferenceToken(ModuleReference? reference)
{
- return AddModuleReference(reference, false);
+ return AddModuleReference(reference, false, false);
}
///
@@ -216,8 +231,11 @@ public MetadataToken GetModuleReferenceToken(ModuleReference? reference)
/// true if the row is always to be added to the end of the buffer, false if a duplicated row
/// is supposed to be removed and the token of the original should be returned instead.
///
+ ///
+ /// true if the metadata token of the module should be preserved, false otherwise.
+ ///
/// The newly assigned metadata token.
- public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates)
+ public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates, bool preserveRid)
{
if (!AssertIsImported(reference))
return MetadataToken.Zero;
@@ -225,7 +243,10 @@ public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDu
var table = Metadata.TablesStream.GetDistinctTable(TableIndex.ModuleRef);
var row = new ModuleReferenceRow(Metadata.StringsStream.GetStringIndex(reference.Name));
- var token = table.Add(row, allowDuplicates);
+ var token = preserveRid
+ ? table.Insert(reference.MetadataToken.Rid, row, allowDuplicates)
+ : table.Add(row, allowDuplicates);
+
AddCustomAttributes(token, reference);
return token;
}
diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs
index 705fda930..7750a6146 100644
--- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs
+++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs
@@ -212,24 +212,27 @@ private void ImportBasicTablesIfSpecified(ModuleDefinition module, DotNetDirecto
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveAssemblyReferenceIndices) != 0)
{
ImportTables(module, TableIndex.AssemblyRef,
- r => buffer.AddAssemblyReference(r, true));
+ r => buffer.AddAssemblyReference(r, true, true));
}
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveModuleReferenceIndices) != 0)
{
ImportTables(module, TableIndex.ModuleRef,
- r => buffer.AddModuleReference(r, true));
+ r => buffer.AddModuleReference(r, true, true));
}
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeReferenceIndices) != 0)
{
ImportTables(module, TableIndex.TypeRef,
- r => buffer.AddTypeReference(r, true));
+ r => buffer.AddTypeReference(r, true, true));
}
}
private void ImportTypeSpecsAndMemberRefsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer)
{
+ if (module.DotNetDirectory is null)
+ return;
+
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeSpecificationIndices) != 0)
{
ImportTables(module, TableIndex.TypeSpec,
diff --git a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs
index 66ff6f0a8..c67b17956 100644
--- a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs
+++ b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs
@@ -70,7 +70,7 @@ public PEImageBuildResult CreateImage(ModuleDefinition module)
};
// Construct new .NET directory.
- var symbolProvider = new NativeSymbolsProvider(image.ImageBase);
+ var symbolProvider = new NativeSymbolsProvider();
var result = DotNetDirectoryFactory.CreateDotNetDirectory(
module,
symbolProvider,
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs
index 2a7ecdef5..ac966743d 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs
@@ -60,6 +60,33 @@ public void EnsureCapacity(int capacity)
///
public MetadataToken Add(in TRow row) => Add(row, false);
+ ///
+ public MetadataToken Insert(uint rid, in TRow row) => Insert(rid, row, false);
+
+ ///
+ /// Inserts a row into the metadata table at the provided row identifier.
+ ///
+ /// The row identifier.
+ /// The row to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ /// The metadata token that this row was assigned to.
+ public MetadataToken Insert(uint rid, in TRow row, bool allowDuplicates)
+ {
+ if (!_entries.TryGetValue(row, out var token))
+ {
+ token = _underlyingBuffer.Insert(rid, in row);
+ _entries.Add(row, token);
+ }
+ else if (allowDuplicates)
+ {
+ token = _underlyingBuffer.Insert(rid, in row);
+ }
+
+ return token;
+ }
+
///
/// Adds a row to the metadata table buffer.
///
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs
index 66a6c1836..b73b5061a 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs
@@ -62,5 +62,13 @@ TRow this[uint rid]
/// The row to add.
/// The metadata token that this row was assigned to.
MetadataToken Add(in TRow row);
+
+ ///
+ /// Inserts a row into the metadata table at the provided row identifier.
+ ///
+ /// The row identifier.
+ /// The row to add.
+ /// The metadata token that this row was assigned to.
+ MetadataToken Insert(uint rid, in TRow row);
}
}
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs
index 8e9d4395f..17717b185 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs
@@ -15,7 +15,9 @@ public class UnsortedMetadataTableBuffer : IMetadataTableBuffer
where TRow : struct, IMetadataRow
{
private readonly RefList _entries = new();
+ private readonly BitList _available = new();
private readonly MetadataTable _table;
+ private uint _currentRid;
///
/// Creates a new unsorted metadata table buffer.
@@ -33,7 +35,11 @@ public UnsortedMetadataTableBuffer(MetadataTable table)
public virtual TRow this[uint rid]
{
get => _entries[(int) (rid - 1)];
- set => _entries[(int) (rid - 1)] = value;
+ set
+ {
+ _entries[(int) (rid - 1)] = value;
+ _available[(int) (rid - 1)] = false;
+ }
}
///
@@ -49,8 +55,45 @@ public void EnsureCapacity(int capacity)
///
public virtual MetadataToken Add(in TRow row)
{
- _entries.Add(row);
- return new MetadataToken(_table.TableIndex, (uint) _entries.Count);
+ // Move over unavailable slots.
+ while (_currentRid < _available.Count && !_available[(int) _currentRid])
+ _currentRid++;
+
+ // If we moved over all entries, we're adding to the end.
+ if (_currentRid == _entries.Count)
+ {
+ _currentRid++;
+ }
+
+ return Insert(_currentRid++, row);
+ }
+
+ ///
+ public MetadataToken Insert(uint rid, in TRow row)
+ {
+ EnsureRowsAllocated(rid);
+
+ var token = new MetadataToken(_table.TableIndex, rid);
+
+ if (!_available[(int) (rid - 1)])
+ {
+ if (EqualityComparer.Default.Equals(row, _entries[(int) (rid - 1)]))
+ return token;
+
+ throw new InvalidOperationException($"Token 0x{token.ToString()} is already in use.");
+ }
+
+ this[rid] = row;
+ return token;
+ }
+
+ private void EnsureRowsAllocated(uint rid)
+ {
+ while (_entries.Count < rid)
+ {
+ _entries.Add(default);
+ _available.Add(true);
+ }
}
///
diff --git a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs
index faec68be5..95aef83eb 100644
--- a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs
+++ b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs
@@ -34,7 +34,7 @@ public IPEImage? ConstructedImage
/// Gets a value indicating whether the image was constructed successfully or not.
///
[MemberNotNullWhen(false, nameof(ConstructedImage))]
- public bool HasFailed => ConstructedImage is null;
+ public bool HasFailed => DiagnosticBag.IsFatal;
///
/// Gets the bag containing the diagnostics that were collected during the construction of the image.
diff --git a/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs b/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs
index 44ceec89c..e0f512a63 100644
--- a/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs
@@ -61,7 +61,7 @@ public void MapTokenToExport(UnmanagedExportInfo exportInfo, MetadataToken token
vtableFixup.Tokens.Add(token);
var vtableSymbol = new Symbol(vtableFixup.Tokens.GetReferenceToIndex(vtableFixup.Tokens.Count - 1));
- var thunkStub = _targetPlatform.CreateThunkStub(0x00400000, vtableSymbol);
+ var thunkStub = _targetPlatform.CreateThunkStub(vtableSymbol);
// Register exported symbol.
var stubReference = thunkStub.Segment.ToReference();
diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs
index 2acc2a57b..c3a6025cf 100644
--- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs
+++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs
@@ -445,7 +445,7 @@ private void WriteFileContents(IBinaryStreamWriter writer, uint alignment)
if (file.Type == BundleFileType.Assembly)
writer.Align(alignment);
- file.Contents.UpdateOffsets(writer.Offset, (uint) writer.Offset);
+ file.Contents.UpdateOffsets(new RelocationParameters(writer.Offset, (uint) writer.Offset));
file.Contents.Write(writer);
}
}
diff --git a/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs
new file mode 100644
index 000000000..efa0063fd
--- /dev/null
+++ b/src/AsmResolver.DotNet/Cloning/CallbackCloneListener.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace AsmResolver.DotNet.Cloning
+{
+ ///
+ /// This implementation that calls the to a callback action.
+ ///
+ public class CallbackCloneListener : MemberClonerListener
+ {
+
+ private readonly Action _callback;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The Callback used.
+ public CallbackCloneListener(Action callback) =>
+ _callback = callback;
+
+ ///
+ public override void OnClonedMember(IMetadataMember original, IMetadataMember cloned) =>
+ _callback(original, cloned);
+ }
+}
diff --git a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs
new file mode 100644
index 000000000..1a6ecaada
--- /dev/null
+++ b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs
@@ -0,0 +1,45 @@
+namespace AsmResolver.DotNet.Cloning
+{
+ ///
+ /// Callback listener that receives calls after cloning process.
+ ///
+ public interface IMemberClonerListener
+ {
+ ///
+ /// This function is called for every member that got cloned.
+ ///
+ /// The original member.
+ /// The cloned member.
+ public void OnClonedMember(IMetadataMember original, IMetadataMember cloned);
+ ///
+ /// This function is called for every type that got cloned.
+ ///
+ /// The original type.
+ /// The cloned type.
+ public void OnClonedType(TypeDefinition original, TypeDefinition cloned);
+ ///
+ /// This function is called for every method that got cloned.
+ ///
+ /// The original method.
+ /// The cloned method.
+ public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned);
+ ///
+ /// This function is called for every field that got cloned.
+ ///
+ /// The original field.
+ /// The cloned field.
+ public void OnClonedField(FieldDefinition original, FieldDefinition cloned);
+ ///
+ /// This function is called for every property that got cloned.
+ ///
+ /// The original property.
+ /// The cloned property.
+ public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned);
+ ///
+ /// This function is called for every event that got cloned.
+ ///
+ /// The original event.
+ /// The cloned event.
+ public void OnClonedEvent(EventDefinition original, EventDefinition cloned);
+ }
+}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs
index b68ab9c81..78df400d3 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs
@@ -39,7 +39,12 @@ private static FieldDefinition CreateFieldStub(MemberCloneContext context, Field
private void DeepCopyFields(MemberCloneContext context)
{
foreach (var field in _fieldsToClone)
+ {
DeepCopyField(context, field);
+ var clonedMember = (FieldDefinition)context.ClonedMembers[field];
+ _clonerListener.OnClonedMember(field, clonedMember);
+ _clonerListener.OnClonedField(field, clonedMember);
+ }
}
private void DeepCopyField(MemberCloneContext context, FieldDefinition field)
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs
index a7c2dc227..af6085a14 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs
@@ -54,7 +54,12 @@ private static ParameterDefinition CloneParameterDefinition(MemberCloneContext c
private void DeepCopyMethods(MemberCloneContext context)
{
foreach (var method in _methodsToClone)
+ {
DeepCopyMethod(context, method);
+ var clonedMember = (MethodDefinition)context.ClonedMembers[method];
+ _clonerListener.OnClonedMember(method, clonedMember);
+ _clonerListener.OnClonedMethod(method, clonedMember);
+ }
}
private void DeepCopyMethod(MemberCloneContext context, MethodDefinition method)
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs
index 10f3f30c4..7d3e5ea6b 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs
@@ -17,6 +17,9 @@ private void DeepCopyProperties(MemberCloneContext context)
{
declaringType.Properties.Add(clonedProperty);
}
+ var clonedMember = clonedProperty;
+ _clonerListener.OnClonedMember(property, clonedMember);
+ _clonerListener.OnClonedProperty(property, clonedMember);
}
}
@@ -51,6 +54,9 @@ private void DeepCopyEvents(MemberCloneContext context)
{
declaringType.Events.Add(clonedEvent);
}
+ var clonedMember = clonedEvent;
+ _clonerListener.OnClonedMember(@event, clonedMember);
+ _clonerListener.OnClonedEvent(@event, clonedMember);
}
}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
index 1580d1efa..af5d27abd 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
@@ -17,6 +17,7 @@ namespace AsmResolver.DotNet.Cloning
///
public partial class MemberCloner
{
+ private readonly IMemberClonerListener _clonerListener;
private readonly Func? _importerFactory;
private readonly ModuleDefinition _targetModule;
@@ -30,18 +31,35 @@ public partial class MemberCloner
/// Creates a new instance of the class.
///
/// The target module to copy the members into.
- public MemberCloner(ModuleDefinition targetModule) : this(targetModule, null) { }
+ public MemberCloner(ModuleDefinition targetModule) : this(targetModule, (original, cloned) => { }) { }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The target module to copy the members into.
+ /// The callback used in the cloner listener.
+ public MemberCloner(ModuleDefinition targetModule, Action callback) : this(targetModule, new CallbackCloneListener(callback)) { }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The target module to copy the members into.
+ /// The callback listener used in the cloner.
+ public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listener) : this(targetModule, null, listener) { }
///
/// Creates a new instance of the class.
///
/// The target module to copy the members into.
/// The factory for creating the reference importer
+ /// The listener used in the cloner.
public MemberCloner(ModuleDefinition targetModule,
- Func? importerFactory)
+ Func? importerFactory,
+ IMemberClonerListener clonerListener)
{
_targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule));
_importerFactory = importerFactory;
+ _clonerListener = clonerListener;
}
///
@@ -272,7 +290,12 @@ private void DeepCopyMembers(MemberCloneContext context)
private void DeepCopyTypes(MemberCloneContext context)
{
foreach (var type in _typesToClone)
+ {
DeepCopyType(context, type);
+ var clonedMember = (TypeDefinition)context.ClonedMembers[type];
+ _clonerListener.OnClonedMember(type, clonedMember);
+ _clonerListener.OnClonedType(type, clonedMember);
+ }
}
private void DeepCopyType(MemberCloneContext context, TypeDefinition type)
diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs
new file mode 100644
index 000000000..49bea854a
--- /dev/null
+++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs
@@ -0,0 +1,19 @@
+namespace AsmResolver.DotNet.Cloning
+{
+ ///
+ public abstract class MemberClonerListener : IMemberClonerListener
+ {
+ ///
+ public virtual void OnClonedMember(IMetadataMember original, IMetadataMember cloned) { }
+ ///
+ public virtual void OnClonedEvent(EventDefinition original, EventDefinition cloned) { }
+ ///
+ public virtual void OnClonedField(FieldDefinition original, FieldDefinition cloned) { }
+ ///
+ public virtual void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) { }
+ ///
+ public virtual void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned) { }
+ ///
+ public virtual void OnClonedType(TypeDefinition original, TypeDefinition cloned) { }
+ }
+}
diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs
index 180e474f8..e1915c5e6 100644
--- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs
+++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs
@@ -130,43 +130,6 @@ public bool VerifyLabelsOnBuild
| (value ? CilMethodBodyBuildFlags.VerifyLabels : 0);
}
- ///
- /// Creates a CIL method body from a dynamic method.
- ///
- /// The method that owns the method body.
- /// The Dynamic Method/Delegate/DynamicResolver.
- /// The method body.
- [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver")]
- public static CilMethodBody FromDynamicMethod(MethodDefinition method, object dynamicMethodObj)
- {
- if (!(method.Module is SerializedModuleDefinition module))
- throw new ArgumentException("Method body should reference a serialized module.");
-
- var result = new CilMethodBody(method);
- dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj);
-
- //Get Runtime Fields
- byte[] code = FieldReader.ReadField(dynamicMethodObj, "m_code")!;
- object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!;
- var tokenList = FieldReader.ReadField>(scope, "m_tokens")!;
- byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!;
- byte[] ehHeader = FieldReader.ReadField(dynamicMethodObj, "m_exceptionHeader")!;
- var ehInfos = FieldReader.ReadField>(dynamicMethodObj, "m_exceptions")!;
-
- //Local Variables
- DynamicMethodHelper.ReadLocalVariables(result, method, localSig);
-
- // Read raw instructions.
- var reader = ByteArrayDataSource.CreateReader(code);
- var disassembler = new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList));
- result.Instructions.AddRange(disassembler.ReadInstructions());
-
- //Exception Handlers
- DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, new ReferenceImporter(module));
-
- return result;
- }
-
///
/// Creates a CIL method body from a raw CIL method body.
///
diff --git a/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs
index 30d0f2181..fa7dfac36 100644
--- a/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs
+++ b/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs
@@ -8,14 +8,6 @@ namespace AsmResolver.DotNet.Code.Native
///
public interface INativeSymbolsProvider
{
- ///
- /// Gets or sets the image base the final PE image is using.
- ///
- ulong ImageBase
- {
- get;
- }
-
///
/// Adds a single symbol to the prototype.
///
diff --git a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs
index e4253b717..aed8247ec 100644
--- a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs
+++ b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs
@@ -18,7 +18,7 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont
var provider = context.SymbolsProvider;
// Create new raw code segment containing the native code.
- var segment = new CodeSegment(provider.ImageBase, nativeMethodBody.Code);
+ var segment = new CodeSegment(nativeMethodBody.Code);
// Process fixups.
for (int i = 0; i < nativeMethodBody.AddressFixups.Count; i++)
diff --git a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs
index 9a1e97273..b09c1e09f 100644
--- a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs
+++ b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs
@@ -22,21 +22,6 @@ public class NativeSymbolsProvider : INativeSymbolsProvider
private uint _maxExportedOrdinal = 0;
private readonly List _floatingExportedSymbols = new();
- ///
- /// Creates a new instance of the class.
- ///
- /// The image base of the final PE image.
- public NativeSymbolsProvider(ulong imageBase)
- {
- ImageBase = imageBase;
- }
-
- ///
- public ulong ImageBase
- {
- get;
- }
-
///
public ISymbol ImportSymbol(ISymbol symbol)
{
diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs
index 30bab5716..18e57b9e5 100644
--- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs
+++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs
@@ -15,7 +15,7 @@ public class DefaultMetadataResolver : IMetadataResolver
private readonly ConcurrentDictionary _typeCache;
private readonly SignatureComparer _comparer = new()
{
- AcceptNewerAssemblyVersionNumbers = true
+ IgnoreAssemblyVersionNumbers = true
};
///
diff --git a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs
index 33813a6ed..d289f45f5 100644
--- a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs
+++ b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs
@@ -53,6 +53,21 @@ public Version Version
get;
}
+ ///
+ /// Gets a value indicating whether the application targets the .NET or .NET Core runtime or not.
+ ///
+ public bool IsNetCoreApp => Name == NetCoreApp;
+
+ ///
+ /// Gets a value indicating whether the application targets the .NET Framework runtime or not.
+ ///
+ public bool IsNetFramework => Name == NetFramework;
+
+ ///
+ /// Gets a value indicating whether the application targets the .NET standard specification or not.
+ ///
+ public bool IsNetStandard => Name == NetStandard;
+
///
/// Attempts to parse the framework name as provided in .
///
diff --git a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet/DynamicMethodDefinition.cs
deleted file mode 100644
index b95d8f003..000000000
--- a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System;
-using System.Reflection;
-using AsmResolver.DotNet.Code.Cil;
-using AsmResolver.DotNet.Signatures;
-using AsmResolver.DotNet.Signatures.Types;
-using AsmResolver.PE.DotNet.Metadata.Tables;
-using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes;
-
-namespace AsmResolver.DotNet
-{
- ///
- /// Represents a single method in a type definition of a .NET module.
- ///
- public class DynamicMethodDefinition : MethodDefinition
- {
- ///
- /// Create a Dynamic Method Definition
- ///
- /// Target Module
- /// Dynamic Method / Delegate / DynamicResolver
- [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver and FromDynamicMethod")]
- public DynamicMethodDefinition(ModuleDefinition module,object dynamicMethodObj) :
- base(new MetadataToken(TableIndex.Method, 0))
- {
- dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj);
- var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method");
- if (methodBase is null)
- {
- throw new ArgumentException(
- "Could not get the underlying method base in the provided dynamic method object.");
- }
-
- Module = module;
- Name = methodBase.Name;
- Attributes = (MethodAttributes)methodBase.Attributes;
- Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase,module));
- CilMethodBody = CilMethodBody.FromDynamicMethod(this,dynamicMethodObj);
- }
-
- private MethodSignature ResolveSig(MethodBase methodBase,ModuleDefinition module)
- {
- var imp = new ReferenceImporter(module);
- var returnType = methodBase is MethodInfo info
- ? imp.ImportTypeSignature(info.ReturnType)
- : module.CorLibTypeFactory.Void;
-
- var parameters = methodBase.GetParameters();
-
- var parameterTypes = new TypeSignature[parameters.Length];
- for (int i = 0; i < parameterTypes.Length; i++)
- parameterTypes[i] = imp.ImportTypeSignature(parameters[i].ParameterType);
-
- return new MethodSignature(
- methodBase.IsStatic ? 0 : CallingConventionAttributes.HasThis,
- returnType, parameterTypes);
- }
-
- ///
- public override ModuleDefinition Module { get; }
-
- }
-}
diff --git a/src/AsmResolver.DotNet/ITypeDefOrRef.cs b/src/AsmResolver.DotNet/ITypeDefOrRef.cs
index a81bc335b..61701cde0 100644
--- a/src/AsmResolver.DotNet/ITypeDefOrRef.cs
+++ b/src/AsmResolver.DotNet/ITypeDefOrRef.cs
@@ -1,3 +1,5 @@
+using AsmResolver.DotNet.Signatures.Types;
+
namespace AsmResolver.DotNet
{
///
@@ -35,5 +37,16 @@ public interface ITypeDefOrRef : ITypeDescriptor, IMemberRefParent, IHasCustomAt
/// The reference importer to use for importing the type.
/// The imported type.
new ITypeDefOrRef ImportWith(ReferenceImporter importer);
+
+ ///
+ /// Transforms the type descriptor to an instance of a , which can be used in
+ /// blob signatures.
+ ///
+ /// true if the type is a value type, false otherwise.
+ /// The constructed type signature instance.
+ ///
+ /// This function can be used to avoid type resolution on type references.
+ ///
+ TypeSignature ToTypeSignature(bool isValueType);
}
}
diff --git a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs
index aa0dc667d..6197f4085 100644
--- a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs
+++ b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs
@@ -90,6 +90,8 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error)
TypeSignature ITypeDescriptor.ToTypeSignature() => throw new InvalidOperationException();
+ TypeSignature ITypeDefOrRef.ToTypeSignature(bool isValueType) => throw new InvalidOperationException();
+
///
public override string ToString() => ((IFullNameProvider) this).Name!;
diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs
index 33d904554..65f1061cd 100644
--- a/src/AsmResolver.DotNet/ReferenceImporter.cs
+++ b/src/AsmResolver.DotNet/ReferenceImporter.cs
@@ -492,6 +492,7 @@ public virtual MethodSpecification ImportMethod(MethodSpecification method)
///
/// The method to import.
/// The imported method.
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls System.Reflection.Module.ResolveMethod(int)")]
public virtual IMethodDescriptor ImportMethod(MethodBase method)
{
if (method is null)
@@ -522,6 +523,7 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method)
return new MemberReference(ImportType(method.DeclaringType), method.Name, result);
}
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls AsmResolver.DotNet.ReferenceImporter.ImportMethod(System.Reflection.MethodBase)")]
private IMethodDescriptor ImportGenericMethod(MethodInfo method)
{
var memberRef = (IMethodDefOrRef) ImportMethod(method.GetGenericMethodDefinition());
@@ -576,6 +578,7 @@ public FieldSignature ImportFieldSignature(FieldSignature signature)
/// The field to import.
/// The imported field.
/// Occurs when a field is not added to a type.
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls System.Reflection.Module.ResolveField(int)")]
public MemberReference ImportField(FieldInfo field)
{
if (field is null)
diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs
index e05310b12..53a414283 100644
--- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs
+++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs
@@ -11,6 +11,8 @@ namespace AsmResolver.DotNet.Signatures.Types
public class GenericInstanceTypeSignature : TypeSignature, IGenericArgumentsProvider
{
private readonly List _typeArguments;
+ private ITypeDefOrRef _genericType;
+ private bool _isValueType;
internal new static GenericInstanceTypeSignature FromReader(in BlobReadContext context, ref BinaryStreamReader reader)
{
@@ -55,9 +57,9 @@ public GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType,
private GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType,
IEnumerable typeArguments)
{
- GenericType = genericType;
+ _genericType = genericType;
_typeArguments = new List(typeArguments);
- IsValueType = isValueType;
+ _isValueType = isValueType;
}
///
@@ -68,8 +70,12 @@ private GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType
///
public ITypeDefOrRef GenericType
{
- get;
- set;
+ get => _genericType;
+ set
+ {
+ _genericType = value;
+ _isValueType = _genericType.IsValueType;
+ }
}
///
@@ -97,10 +103,7 @@ public override string? Name
public override ModuleDefinition? Module => GenericType.Module;
///
- public override bool IsValueType
- {
- get;
- }
+ public override bool IsValueType => _isValueType;
///
public override TypeDefinition? Resolve() => GenericType.Resolve();
diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs
index cab577dc4..f617beb31 100644
--- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs
+++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs
@@ -7,6 +7,9 @@ namespace AsmResolver.DotNet.Signatures.Types
///
public class TypeDefOrRefSignature : TypeSignature
{
+ private ITypeDefOrRef _type;
+ private bool _isValueType;
+
///
/// Creates a new type signature referencing a type in a type metadata table.
///
@@ -23,9 +26,8 @@ public TypeDefOrRefSignature(ITypeDefOrRef type)
/// Indicates whether the referenced type is a value type or not.
public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType)
{
- Type = type;
- IsValueType = isValueType;
-
+ _type = type;
+ _isValueType = isValueType;
}
///
@@ -33,7 +35,12 @@ public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType)
///
public ITypeDefOrRef Type
{
- get;
+ get => _type;
+ set
+ {
+ _type = value;
+ _isValueType = value.IsValueType;
+ }
}
///
@@ -52,10 +59,7 @@ public ITypeDefOrRef Type
public override ModuleDefinition? Module => Type.Module;
///
- public override bool IsValueType
- {
- get;
- }
+ public override bool IsValueType => _isValueType;
///
public override TypeDefinition? Resolve() => Type.Resolve();
diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs
index 389411e30..7eb81bfbd 100644
--- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs
+++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs
@@ -14,18 +14,6 @@ public abstract class TypeSignature : ExtendableBlobSignature, ITypeDescriptor
{
internal const string NullTypeToString = "<??>>";
- private static readonly MethodInfo GetTypeFromHandleUnsafeMethod;
-
- static TypeSignature()
- {
- GetTypeFromHandleUnsafeMethod = typeof(Type)
- .GetMethod("GetTypeFromHandleUnsafe",
- (BindingFlags) (-1),
- null,
- new[] {typeof(IntPtr)},
- null)!;
- }
-
///
public abstract string? Name
{
@@ -156,19 +144,9 @@ public static TypeSignature FromReader(in BlobReadContext context, ref BinaryStr
return new BoxedTypeSignature(FromReader(context, ref reader));
case ElementType.Internal:
- var address = IntPtr.Size switch
- {
- 4 => new IntPtr(reader.ReadInt32()),
- _ => new IntPtr(reader.ReadInt64())
- };
-
- // Let the runtime translate the address to a type and import it.
- var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address });
- var asmResType = clrType is not null
- ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType)
- : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec);
-
- return new TypeDefOrRefSignature(asmResType);
+ throw new NotSupportedException(
+ "Encountered an COR_ELEMENT_TYPE_INTERNAL type signature which is not supported by this "
+ + " type signature reader. Use the AsmResolver.DotNet.Dynamic extension package instead.");
default:
throw new ArgumentOutOfRangeException($"Invalid or unsupported element type {elementType}.");
diff --git a/src/AsmResolver.DotNet/TokenAllocator.cs b/src/AsmResolver.DotNet/TokenAllocator.cs
index f02d606df..9f1cff05a 100644
--- a/src/AsmResolver.DotNet/TokenAllocator.cs
+++ b/src/AsmResolver.DotNet/TokenAllocator.cs
@@ -38,6 +38,9 @@ private void InitializeTable(IDotNetDirectory netDirectory)
var tableStream = netDirectory.Metadata!.GetStream();
for (TableIndex index = 0; index < TableIndex.Max; index++)
{
+ if (!index.IsValidTableIndex())
+ continue;
+
var table = tableStream.GetTable(index);
_buckets[(int) index] = new TokenBucket(new MetadataToken(index, (uint) table.Count + 1));
}
diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs
index ce14eb497..faecf8fbd 100644
--- a/src/AsmResolver.DotNet/TypeDefinition.cs
+++ b/src/AsmResolver.DotNet/TypeDefinition.cs
@@ -674,10 +674,13 @@ public bool Implements(string fullName)
ITypeDefOrRef ITypeDescriptor.ToTypeDefOrRef() => this;
///
- public TypeSignature ToTypeSignature()
+ public TypeSignature ToTypeSignature() => ToTypeSignature(IsValueType);
+
+ ///
+ public TypeSignature ToTypeSignature(bool isValueType)
{
return Module?.CorLibTypeFactory.FromType(this) as TypeSignature
- ?? new TypeDefOrRefSignature(this, IsValueType);
+ ?? new TypeDefOrRefSignature(this, isValueType);
}
///
diff --git a/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs b/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs
index a108f6376..360ad9f54 100644
--- a/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs
+++ b/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs
@@ -100,7 +100,24 @@ public static CustomModifierTypeSignature MakeModifierType(
public static GenericInstanceTypeSignature MakeGenericInstanceType(
this ITypeDescriptor type, params TypeSignature[] typeArguments)
{
- return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), type.IsValueType, typeArguments);
+ return type.MakeGenericInstanceType(type.IsValueType, typeArguments);
+ }
+
+ ///
+ /// Constructs a new pointer type signature with the provided type descriptor as element type.
+ /// as element type.
+ ///
+ /// The element type.
+ /// true if the type is a value type, false otherwise.
+ /// The arguments to instantiate the type with.
+ /// The constructed by-reference type signature.
+ ///
+ /// This function can be used to avoid type resolution on type references.
+ ///
+ public static GenericInstanceTypeSignature MakeGenericInstanceType(
+ this ITypeDescriptor type, bool isValueType, params TypeSignature[] typeArguments)
+ {
+ return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), isValueType, typeArguments);
}
///
diff --git a/src/AsmResolver.DotNet/TypeReference.cs b/src/AsmResolver.DotNet/TypeReference.cs
index 0b1332fe9..fef21335e 100644
--- a/src/AsmResolver.DotNet/TypeReference.cs
+++ b/src/AsmResolver.DotNet/TypeReference.cs
@@ -130,10 +130,13 @@ public IList CustomAttributes
ITypeDefOrRef ITypeDescriptor.ToTypeDefOrRef() => this;
///
- public TypeSignature ToTypeSignature()
+ public TypeSignature ToTypeSignature() => ToTypeSignature(IsValueType);
+
+ ///
+ public TypeSignature ToTypeSignature(bool isValueType)
{
return Module?.CorLibTypeFactory.FromType(this) as TypeSignature
- ?? new TypeDefOrRefSignature(this, IsValueType);
+ ?? new TypeDefOrRefSignature(this, isValueType);
}
///
diff --git a/src/AsmResolver.DotNet/TypeSpecification.cs b/src/AsmResolver.DotNet/TypeSpecification.cs
index e8d805b4d..aa4420351 100644
--- a/src/AsmResolver.DotNet/TypeSpecification.cs
+++ b/src/AsmResolver.DotNet/TypeSpecification.cs
@@ -95,6 +95,9 @@ public IList CustomAttributes
public TypeSignature ToTypeSignature() =>
Signature ?? throw new ArgumentException("Signature embedded into the type specification is null.");
+ ///
+ public TypeSignature ToTypeSignature(bool isValueType) => ToTypeSignature();
+
///
public bool IsImportedInModule(ModuleDefinition module) => Signature?.IsImportedInModule(module) ?? false;
diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj
index 5c0bfda0f..98b14d222 100644
--- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj
+++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj
@@ -25,7 +25,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/AsmResolver.PE.File/IPEFile.cs b/src/AsmResolver.PE.File/IPEFile.cs
index 57e4e50a9..abc25a417 100644
--- a/src/AsmResolver.PE.File/IPEFile.cs
+++ b/src/AsmResolver.PE.File/IPEFile.cs
@@ -9,7 +9,7 @@ namespace AsmResolver.PE.File
///
/// Provides a writable implementation of the interface.
///
- public interface IPEFile : ISegmentReferenceResolver, IOffsetConverter
+ public interface IPEFile : ISegmentReferenceFactory, IOffsetConverter
{
///
/// When this PE file was read from the disk, gets the file path to the PE file.
diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs
index d1dc0f2d7..6ce7616e2 100644
--- a/src/AsmResolver.PE.File/PEFile.cs
+++ b/src/AsmResolver.PE.File/PEFile.cs
@@ -371,12 +371,15 @@ public void UpdateHeaders()
FileHeader.NumberOfSections = (ushort) Sections.Count;
- FileHeader.UpdateOffsets(
+ var relocation = new RelocationParameters(OptionalHeader.ImageBase, 0, 0,
+ OptionalHeader.Magic == OptionalHeaderMagic.Pe32);
+
+ FileHeader.UpdateOffsets(relocation.WithOffsetRva(
DosHeader.NextHeaderOffset + 4,
- DosHeader.NextHeaderOffset + 4);
- OptionalHeader.UpdateOffsets(
+ DosHeader.NextHeaderOffset + 4));
+ OptionalHeader.UpdateOffsets(relocation.WithOffsetRva(
FileHeader.Offset + FileHeader.GetPhysicalSize(),
- FileHeader.Rva + FileHeader.GetVirtualSize());
+ FileHeader.Rva + FileHeader.GetVirtualSize()));
FileHeader.SizeOfOptionalHeader = (ushort) OptionalHeader.GetPhysicalSize();
OptionalHeader.SizeOfHeaders = (uint) (OptionalHeader.Offset
@@ -391,7 +394,9 @@ public void UpdateHeaders()
OptionalHeader.SizeOfImage = lastSection.Rva
+ lastSection.GetVirtualSize().Align(OptionalHeader.SectionAlignment);
- EofData?.UpdateOffsets(lastSection.Offset + lastSection.GetPhysicalSize(), OptionalHeader.SizeOfImage);
+ EofData?.UpdateOffsets(relocation.WithOffsetRva(
+ lastSection.Offset + lastSection.GetPhysicalSize(),
+ OptionalHeader.SizeOfImage));
}
///
@@ -399,19 +404,20 @@ public void UpdateHeaders()
///
public void AlignSections()
{
- uint currentFileOffset = OptionalHeader.SizeOfHeaders;
+ var relocation = new RelocationParameters(
+ OptionalHeader.ImageBase,
+ OptionalHeader.SizeOfHeaders.Align(OptionalHeader.FileAlignment),
+ OptionalHeader.SizeOfHeaders.Align(OptionalHeader.SectionAlignment),
+ OptionalHeader.Magic == OptionalHeaderMagic.Pe32);
for (int i = 0; i < Sections.Count; i++)
{
var section = Sections[i];
- uint rva = i > 0
- ? Sections[i - 1].Rva + Sections[i - 1].GetVirtualSize()
- : OptionalHeader.SizeOfHeaders.Align(OptionalHeader.SectionAlignment);
-
- currentFileOffset = currentFileOffset.Align(OptionalHeader.FileAlignment);
- section.UpdateOffsets(currentFileOffset, rva.Align(OptionalHeader.SectionAlignment));
- currentFileOffset += section.GetPhysicalSize();
+ section.UpdateOffsets(relocation);
+ relocation.Advance(
+ section.GetPhysicalSize().Align(OptionalHeader.FileAlignment),
+ section.GetVirtualSize().Align(OptionalHeader.SectionAlignment));
}
}
diff --git a/src/AsmResolver.PE.File/PESection.cs b/src/AsmResolver.PE.File/PESection.cs
index 8338dd51f..b091780ae 100644
--- a/src/AsmResolver.PE.File/PESection.cs
+++ b/src/AsmResolver.PE.File/PESection.cs
@@ -216,7 +216,7 @@ public ISegment? Contents
public bool CanUpdateOffsets => true;
///
- public void UpdateOffsets(ulong newOffset, uint newRva) => Contents?.UpdateOffsets(newOffset, newRva);
+ public void UpdateOffsets(in RelocationParameters parameters) => Contents?.UpdateOffsets(parameters);
///
public uint GetPhysicalSize() => Contents?.GetPhysicalSize() ?? 0;
diff --git a/src/AsmResolver.PE.File/PESegmentReference.cs b/src/AsmResolver.PE.File/PESegmentReference.cs
index 5c7326cbc..9eeeaea0a 100644
--- a/src/AsmResolver.PE.File/PESegmentReference.cs
+++ b/src/AsmResolver.PE.File/PESegmentReference.cs
@@ -32,18 +32,12 @@ public uint Rva
get;
}
- ///
- bool IOffsetProvider.CanUpdateOffsets => false;
-
///
public bool CanRead => _peFile.TryGetSectionContainingRva(Rva, out _);
///
public bool IsBounded => false;
- ///
- void IOffsetProvider.UpdateOffsets(ulong newOffset, uint newRva) => throw new InvalidOperationException();
-
///
public BinaryStreamReader CreateReader() => _peFile.CreateReaderAtRva(Rva);
diff --git a/src/AsmResolver.PE.File/SerializedPEFile.cs b/src/AsmResolver.PE.File/SerializedPEFile.cs
index c9bff4dab..a29c30d1b 100644
--- a/src/AsmResolver.PE.File/SerializedPEFile.cs
+++ b/src/AsmResolver.PE.File/SerializedPEFile.cs
@@ -12,6 +12,8 @@ public class SerializedPEFile : PEFile
{
private readonly List _sectionHeaders;
private readonly BinaryStreamReader _reader;
+ private readonly ulong _originalImageBase;
+ private readonly bool _is32Bit;
///
/// Reads a PE file from an input stream.
@@ -36,6 +38,8 @@ public SerializedPEFile(in BinaryStreamReader reader, PEMappingMode mode)
// Read NT headers.
FileHeader = FileHeader.FromReader(ref _reader);
OptionalHeader = OptionalHeader.FromReader(ref _reader);
+ _originalImageBase = OptionalHeader.ImageBase;
+ _is32Bit = OptionalHeader.Magic == OptionalHeaderMagic.Pe32;
// Read section headers.
_reader.Offset = OptionalHeader.Offset + FileHeader.SizeOfOptionalHeader;
@@ -70,7 +74,12 @@ protected override IList GetSections()
physicalContents = new DataSourceSegment(_reader.DataSource, offset, header.VirtualAddress, size);
var virtualSegment = new VirtualSegment(physicalContents, header.VirtualSize);
- virtualSegment.UpdateOffsets(offset, header.VirtualAddress);
+ virtualSegment.UpdateOffsets(new RelocationParameters(
+ _originalImageBase,
+ offset,
+ header.VirtualAddress,
+ _is32Bit));
+
result.Add(new PESection(header, virtualSegment));
}
diff --git a/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs b/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs
index 03e8f4979..d698bca04 100644
--- a/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs
+++ b/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs
@@ -24,7 +24,7 @@ public class FixedVersionInfo : SegmentBase
public static FixedVersionInfo FromReader(ref BinaryStreamReader reader)
{
var result = new FixedVersionInfo();
- result.UpdateOffsets(reader.Offset, reader.Rva);
+ result.UpdateOffsets(new RelocationParameters(reader.Offset, reader.Rva));
uint signature = reader.ReadUInt32();
if (signature != Signature)
diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj
index 058547040..a008308e1 100644
--- a/src/AsmResolver.PE/AsmResolver.PE.csproj
+++ b/src/AsmResolver.PE/AsmResolver.PE.csproj
@@ -24,7 +24,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/AsmResolver.PE/Code/CodeSegment.cs b/src/AsmResolver.PE/Code/CodeSegment.cs
index eac4e00ef..b392e146e 100644
--- a/src/AsmResolver.PE/Code/CodeSegment.cs
+++ b/src/AsmResolver.PE/Code/CodeSegment.cs
@@ -56,6 +56,13 @@ public IList AddressFixups
get;
} = new List();
+ ///
+ public override void UpdateOffsets(in RelocationParameters parameters)
+ {
+ ImageBase = parameters.ImageBase;
+ base.UpdateOffsets(in parameters);
+ }
+
///
public override uint GetPhysicalSize() => (uint) Code.Length;
diff --git a/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs b/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs
index bf5ce6ac0..e5afa4e88 100644
--- a/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs
+++ b/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs
@@ -41,7 +41,7 @@ public void AddEntry(DebugDataEntry entry)
public bool CanUpdateOffsets => true;
///
- public void UpdateOffsets(ulong newOffset, uint newRva) => _headers.UpdateOffsets(newOffset, newRva);
+ public void UpdateOffsets(in RelocationParameters parameters) => _headers.UpdateOffsets(parameters);
///
public uint GetPhysicalSize() => _headers.GetPhysicalSize();
diff --git a/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs b/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs
index ec05a4438..98af4faf0 100644
--- a/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs
+++ b/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs
@@ -45,10 +45,10 @@ public ISegment? Contents
public bool CanUpdateOffsets => Contents?.CanUpdateOffsets ?? false;
///
- public void UpdateOffsets(ulong newOffset, uint newRva)
+ public void UpdateOffsets(in RelocationParameters parameters)
{
if (Contents != null)
- Contents.UpdateOffsets(newOffset, newRva);
+ Contents.UpdateOffsets(parameters);
else
throw new ArgumentNullException(nameof(Contents));
}
diff --git a/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs b/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs
index 44cfd21ef..fe345de97 100644
--- a/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs
+++ b/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs
@@ -73,7 +73,7 @@ private void AddIfPresent(ISegment? segment)
}
///
- public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva);
+ public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters);
///
public uint GetPhysicalSize() => _segments.GetPhysicalSize();
diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs
index 9c19fbc71..56fb4fb7d 100644
--- a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs
+++ b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs
@@ -262,7 +262,7 @@ private static void CreateImportDirectory(IPEImage image, ManagedPEBuilderContex
if (entrypointSymbol is null)
throw new InvalidOperationException("Entrypoint symbol was required but not imported.");
- context.Bootstrapper = context.Platform.CreateThunkStub(image.ImageBase, entrypointSymbol);
+ context.Bootstrapper = context.Platform.CreateThunkStub(entrypointSymbol);
}
}
diff --git a/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs b/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs
index 71d971514..76b0a93f5 100644
--- a/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs
+++ b/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs
@@ -53,7 +53,7 @@ public void AddCilBody(CilRawMethodBody body)
public void AddNativeBody(ISegment body, uint alignment) => _nativeBodies.Add(body, alignment);
///
- public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva);
+ public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters);
///
public uint GetPhysicalSize()
diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs
index 69a4a9582..2d22d134a 100644
--- a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs
+++ b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs
@@ -153,7 +153,7 @@ public IList ExtraSections
// Create body.
var body = new CilRawFatMethodBody(flags, maxStack, localVarSigToken, code);
- body.UpdateOffsets(fileOffset, rva);
+ body.UpdateOffsets(new RelocationParameters(fileOffset, rva));
// Read any extra sections.
if (body.HasSections)
@@ -172,18 +172,24 @@ public IList ExtraSections
}
///
- public override void UpdateOffsets(ulong newOffset, uint newRva)
+ public override void UpdateOffsets(in RelocationParameters parameters)
{
- base.UpdateOffsets(newOffset, newRva);
- Code.UpdateOffsets(newOffset + 12, newRva + 12);
+ base.UpdateOffsets(parameters);
+
+ var current = parameters.WithAdvance(12);
+
+ Code.UpdateOffsets(current);
if (HasSections)
{
uint codeSize = Code.GetPhysicalSize();
- newOffset = (Code.Offset + codeSize).Align(4);
- newRva = (Code.Rva + codeSize).Align(4);
+ current.Advance(codeSize);
+ current.Align(4);
for (int i = 0; i < ExtraSections.Count; i++)
- ExtraSections[i].UpdateOffsets(newOffset, newRva);
+ {
+ ExtraSections[i].UpdateOffsets(current);
+ current.Advance(ExtraSections[i].GetPhysicalSize());
+ }
}
}
diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs
index 5c35165ad..852af652b 100644
--- a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs
+++ b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs
@@ -58,15 +58,15 @@ public CilRawTinyMethodBody(IReadableSegment code)
uint codeSize = (uint) flag >> 2;
var methodBody = new CilRawTinyMethodBody(reader.ReadSegment(codeSize));
- methodBody.UpdateOffsets(fileOffset, rva);
+ methodBody.UpdateOffsets(new RelocationParameters(fileOffset, rva));
return methodBody;
}
///
- public override void UpdateOffsets(ulong newOffset, uint newRva)
+ public override void UpdateOffsets(in RelocationParameters parameters)
{
- base.UpdateOffsets(newOffset, newRva);
- Code.UpdateOffsets(newOffset + 1, newRva + 1);
+ base.UpdateOffsets(parameters);
+ Code.UpdateOffsets(parameters.WithAdvance(1));
}
///
diff --git a/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs b/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs
index b39a0ba3f..056f2b274 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs
@@ -68,7 +68,7 @@ public BinaryStreamReader CreateReader()
}
///
- public void UpdateOffsets(ulong newOffset, uint newRva) => Contents.UpdateOffsets(newOffset, newRva);
+ public void UpdateOffsets(in RelocationParameters parameters) => Contents.UpdateOffsets(parameters);
///
public uint GetPhysicalSize() => Contents.GetPhysicalSize();
diff --git a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs
index 5927c54b5..2c4d3364c 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs
@@ -1,7 +1,7 @@
-using System;
using AsmResolver.IO;
using AsmResolver.PE.DotNet.Metadata.Blob;
using AsmResolver.PE.DotNet.Metadata.Guid;
+using AsmResolver.PE.DotNet.Metadata.Pdb;
using AsmResolver.PE.DotNet.Metadata.Strings;
using AsmResolver.PE.DotNet.Metadata.Tables;
using AsmResolver.PE.DotNet.Metadata.UserStrings;
@@ -15,8 +15,17 @@ namespace AsmResolver.PE.DotNet.Metadata
///
public class DefaultMetadataStreamReader : IMetadataStreamReader
{
+ ///
+ /// Gets a default instance
+ ///
+ public static DefaultMetadataStreamReader Instance
+ {
+ get;
+ } = new();
+
///
- public IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader header,
+ public IMetadataStream ReadStream(MetadataReaderContext context,
+ MetadataStreamHeader header,
ref BinaryStreamReader reader)
{
switch (header.Name)
@@ -37,6 +46,9 @@ public IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader
case GuidStream.DefaultName:
return new SerializedGuidStream(header.Name, reader);
+ case PdbStream.DefaultName:
+ return new SerializedPdbStream(header.Name, reader);
+
default:
return new CustomMetadataStream(header.Name, DataSegment.FromReader(ref reader));
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs b/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs
new file mode 100644
index 000000000..0877d6e7d
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs
@@ -0,0 +1,14 @@
+namespace AsmResolver.PE.DotNet.Metadata
+{
+ ///
+ /// Represents a metadata stream that is initialized lazily.
+ ///
+ public interface ILazyMetadataStream : IMetadataStream
+ {
+ ///
+ /// Finalizes the initialization process of the metadata stream.
+ ///
+ /// The metadata directory that defines the stream.
+ void Initialize(IMetadata parentMetadata);
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs
index 319459f43..01a4ed0c1 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs
@@ -14,6 +14,6 @@ public interface IMetadataStreamReader
/// The header of the metadata stream.
/// The input stream to read from.
/// The read metadata stream.
- IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader);
+ IMetadataStream ReadStream(MetadataReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader);
}
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs
index cc1b88b5c..9bc2e415a 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs
@@ -60,6 +60,48 @@ public IList Streams
}
}
+ ///
+ /// Reads a .NET metadata directory from a file.
+ ///
+ /// The path to the file.
+ /// The read metadata.
+ public static Metadata FromFile(string path) => FromBytes(System.IO.File.ReadAllBytes(path));
+
+ ///
+ /// Interprets the provided binary data as a .NET metadata directory.
+ ///
+ /// The raw data.
+ /// The read metadata.
+ public static Metadata FromBytes(byte[] data) => FromReader(ByteArrayDataSource.CreateReader(data));
+
+ ///
+ /// Reads a .NET metadata directory from a file.
+ ///
+ /// The file to read.
+ /// The read metadata.
+ public static Metadata FromFile(IInputFile file) => FromReader(file.CreateReader());
+
+ ///
+ /// Interprets the provided binary stream as a .NET metadata directory.
+ ///
+ /// The input stream.
+ /// The read metadata.
+ public static Metadata FromReader(BinaryStreamReader reader)
+ {
+ return FromReader(reader, new MetadataReaderContext(VirtualAddressFactory.Instance));
+ }
+
+ ///
+ /// Interprets the provided binary stream as a .NET metadata directory.
+ ///
+ /// The input stream.
+ /// The context in which the reader is situated in.
+ /// The read metadata.
+ public static Metadata FromReader(BinaryStreamReader reader, MetadataReaderContext context)
+ {
+ return new SerializedMetadata(context, ref reader);
+ }
+
///
public override uint GetPhysicalSize()
{
diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs
new file mode 100644
index 000000000..e16c9100b
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs
@@ -0,0 +1,75 @@
+using System;
+
+namespace AsmResolver.PE.DotNet.Metadata
+{
+ ///
+ /// Provides a context for a .NET metadata directory reader.
+ ///
+ public class MetadataReaderContext : IErrorListener
+ {
+ ///
+ /// Constructs a new metadata reader context.
+ ///
+ /// The factory object responsible for translating RVAs to references.
+ public MetadataReaderContext(ISegmentReferenceFactory factory)
+ : this(factory, ThrowErrorListener.Instance, DefaultMetadataStreamReader.Instance)
+ {
+ }
+
+ ///
+ /// Constructs a new metadata reader context.
+ ///
+ /// The factory object responsible for translating RVAs to references.
+ /// The object responsible for collecting any errors during the parsing.
+ /// The object responsible for reading metadata streams in the .NET data directory.
+ public MetadataReaderContext(
+ ISegmentReferenceFactory referenceFactory,
+ IErrorListener errorListener,
+ IMetadataStreamReader metadataStreamReader)
+ {
+ ReferenceFactory = referenceFactory;
+ ErrorListener = errorListener;
+ MetadataStreamReader = metadataStreamReader;
+ }
+
+ ///
+ /// Gets the factory responsible for translating RVAs to references.
+ ///
+ public ISegmentReferenceFactory ReferenceFactory
+ {
+ get;
+ }
+
+ ///
+ /// Gets the object responsible for collecting any errors during the parsing.
+ ///
+ public IErrorListener ErrorListener
+ {
+ get;
+ }
+
+ ///
+ /// Gets the object responsible for reading metadata streams in the .NET data directory.
+ ///
+ public IMetadataStreamReader MetadataStreamReader
+ {
+ get;
+ }
+
+ ///
+ /// Constructs a metadata reader context from a PE reader context.
+ ///
+ /// The context to transform.
+ /// The constructed context.
+ public static MetadataReaderContext FromReaderContext(PEReaderContext context) => new(
+ context.File,
+ context.Parameters.ErrorListener,
+ context.Parameters.MetadataStreamReader);
+
+ ///
+ public void MarkAsFatal() => ErrorListener.MarkAsFatal();
+
+ ///
+ public void RegisterException(Exception exception) => ErrorListener.RegisterException(exception);
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs
index aaad91be3..0fe56918b 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs
@@ -9,25 +9,29 @@ namespace AsmResolver.PE.DotNet.Metadata
///
public class MetadataStreamList : LazyList
{
- private readonly PEReaderContext _context;
+ private readonly MetadataReaderContext _context;
+ private readonly IMetadata _owner;
private readonly int _numberOfStreams;
- private BinaryStreamReader _directoryReader;
+ private readonly BinaryStreamReader _directoryReader;
private BinaryStreamReader _entriesReader;
///
/// Prepares a new lazy-initialized metadata stream list.
///
+ /// The owner of the metadata stream list.
/// The reader context.
/// The input stream containing the metadata directory.
/// The input stream containing the metadata stream entries.
/// The number of streams.
public MetadataStreamList(
- PEReaderContext context,
+ IMetadata owner,
+ MetadataReaderContext context,
in BinaryStreamReader directoryReader,
in BinaryStreamReader entriesReader,
int numberOfStreams)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
+ _owner = owner;
_directoryReader = directoryReader;
_entriesReader = entriesReader;
_numberOfStreams = numberOfStreams;
@@ -48,11 +52,20 @@ protected override void Initialize()
var header = headers[i];
var streamReader = _directoryReader.ForkAbsolute(_directoryReader.Offset + header.Offset, headers[i].Size);
- var stream = _context.Parameters.MetadataStreamReader.ReadStream(_context, header, ref streamReader);
+ var stream = _context.MetadataStreamReader.ReadStream(_context, header, ref streamReader);
Items.Add(stream);
}
}
+ ///
+ protected override void PostInitialize()
+ {
+ for (int i = 0; i < _numberOfStreams; i++)
+ {
+ if (Items[i] is ILazyMetadataStream lazyStream)
+ lazyStream.Initialize(_owner);
+ }
+ }
}
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs
new file mode 100644
index 000000000..4c4d7e002
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Linq;
+using AsmResolver.IO;
+using AsmResolver.PE.DotNet.Metadata.Tables;
+
+namespace AsmResolver.PE.DotNet.Metadata.Pdb
+{
+ ///
+ /// Represents the metadata stream containing Portable PDB debug data that is associated to a .NET module.
+ ///
+ public class PdbStream : SegmentBase, IMetadataStream
+ {
+ ///
+ /// The default name of a PDB stream, as described in the specification provided by Portable PDB v1.0.
+ ///
+ public const string DefaultName = "#Pdb";
+
+ ///
+ public string Name
+ {
+ get;
+ set;
+ } = DefaultName;
+
+ ///
+ public virtual bool CanRead => false;
+
+ ///
+ /// Gets the unique identifier representing the debugging metadata blob content.
+ ///
+ public byte[] Id
+ {
+ get;
+ } = new byte[20];
+
+ ///
+ /// Gets or sets the token of the entry point method, or 9 if not applicable.
+ ///
+ ///
+ /// This should be the same value as stored in the metadata header.
+ ///
+ public MetadataToken EntryPoint
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets an array of row counts of every portable PDB table in the tables stream.
+ ///
+ public uint[] TypeSystemRowCounts
+ {
+ get;
+ } = new uint[(int) TableIndex.Max];
+
+ ///
+ /// Synchronizes the row counts stored in with the tables in the provided
+ /// tables stream.
+ ///
+ /// The tables stream to pull the data from.
+ public void UpdateRowCounts(TablesStream stream)
+ {
+ for (TableIndex i = 0; i < TableIndex.MaxTypeSystemTableIndex; i++)
+ {
+ if (i.IsValidTableIndex())
+ TypeSystemRowCounts[(int) i] = (uint) stream.GetTable(i).Count;
+ }
+ }
+
+ ///
+ /// Synchronizes the row counts stored in with the tables in the provided
+ /// tables stream row counts.
+ ///
+ /// The tables stream row counts to pull in.
+ public void UpdateRowCounts(uint[] rowCounts)
+ {
+ for (TableIndex i = 0; i < TableIndex.MaxTypeSystemTableIndex && (int) i < rowCounts.Length; i++)
+ {
+ if (i.IsValidTableIndex())
+ TypeSystemRowCounts[(int) i] = rowCounts[(int) i];
+ }
+ }
+
+ ///
+ /// Computes the valid bitmask for the type system table rows referenced by this pdb stream.
+ ///
+ /// The bitmask.
+ public ulong ComputeReferencedTypeSystemTables()
+ {
+ ulong result = 0;
+
+ for (int i = 0; i < TypeSystemRowCounts.Length; i++)
+ {
+ if (TypeSystemRowCounts[i] != 0)
+ result |= 1UL << i;
+ }
+
+ return result;
+ }
+
+ ///
+ public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException();
+
+ ///
+ public override uint GetPhysicalSize()
+ {
+ return 20 // ID
+ + sizeof(uint) // EntryPoint
+ + sizeof(ulong) // ReferencedTypeSystemTables
+ + 4 * (uint) TypeSystemRowCounts.Count(c => c != 0); // TypeSystemTableRows.
+ }
+
+ ///
+ public override void Write(IBinaryStreamWriter writer)
+ {
+ writer.WriteBytes(Id);
+ writer.WriteUInt32(EntryPoint.ToUInt32());
+ writer.WriteUInt64(ComputeReferencedTypeSystemTables());
+
+ foreach (uint count in TypeSystemRowCounts)
+ {
+ if (count != 0)
+ writer.WriteUInt32(count);
+ }
+ }
+
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs
new file mode 100644
index 000000000..d6603f0b8
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs
@@ -0,0 +1,63 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Pdb
+{
+ ///
+ /// Provides an implementation of a PDB stream that obtains GUIDs from a readable segment in a file.
+ ///
+ public class SerializedPdbStream : PdbStream
+ {
+ private readonly BinaryStreamReader _reader;
+
+ ///
+ /// Creates a new PDB stream with the provided byte array as the raw contents of the stream.
+ ///
+ /// The raw contents of the stream.
+ public SerializedPdbStream(byte[] rawData)
+ : this(DefaultName, ByteArrayDataSource.CreateReader(rawData))
+ {
+ }
+
+ ///
+ /// Creates a new PDB stream with the provided byte array as the raw contents of the stream.
+ ///
+ /// The name of the stream.
+ /// The raw contents of the stream.
+ public SerializedPdbStream(string name, byte[] rawData)
+ : this(name, ByteArrayDataSource.CreateReader(rawData))
+ {
+ }
+
+ ///
+ /// Creates a new PDB stream with the provided file segment reader as the raw contents of the stream.
+ ///
+ /// The name of the stream.
+ /// The raw contents of the stream.
+ public SerializedPdbStream(string name, in BinaryStreamReader reader)
+ {
+ _reader = reader;
+
+ Name = name;
+ Offset = reader.Offset;
+ Rva = reader.Rva;
+
+ var headerReader = reader.Fork();
+
+ headerReader.ReadBytes(Id, 0, Id.Length);
+ EntryPoint = headerReader.ReadUInt32();
+
+ ulong mask = headerReader.ReadUInt64();
+ for (int i = 0; i < 64; i++)
+ {
+ if (((mask >> i) & 1) != 0)
+ TypeSystemRowCounts[i] = headerReader.ReadUInt32();
+ }
+ }
+
+ ///
+ public override bool CanRead => true;
+
+ ///
+ public override BinaryStreamReader CreateReader() => _reader.Fork();
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs
index 1c3ab7bb2..74cecd3da 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs
@@ -10,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata
///
public class SerializedMetadata : Metadata
{
- private readonly PEReaderContext _context;
+ private readonly MetadataReaderContext _context;
private readonly BinaryStreamReader _streamEntriesReader;
private readonly BinaryStreamReader _streamContentsReader;
private readonly int _numberOfStreams;
@@ -23,7 +23,7 @@ public class SerializedMetadata : Metadata
/// Occurs when any of the arguments are null .
/// Occurs when an unsupported metadata directory format was encountered.
/// Occurs when the metadata directory header is invalid.
- public SerializedMetadata(PEReaderContext context, ref BinaryStreamReader directoryReader)
+ public SerializedMetadata(MetadataReaderContext context, ref BinaryStreamReader directoryReader)
{
if (!directoryReader.IsValid)
throw new ArgumentNullException(nameof(directoryReader));
@@ -75,11 +75,10 @@ protected override IList GetStreams()
if (_numberOfStreams == 0)
return base.GetStreams();
- return new MetadataStreamList(
+ return new MetadataStreamList(this,
_context,
_streamContentsReader,
- _streamEntriesReader,
- _numberOfStreams);
+ _streamEntriesReader, _numberOfStreams);
}
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs
index 55d2f9299..293562f36 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs
@@ -65,22 +65,7 @@ public SerializedStringsStream(string name, in BinaryStreamReader reader)
if (!_cachedStrings.TryGetValue(index, out var value) && index < _reader.Length)
{
var stringsReader = _reader.ForkRelative(index);
- byte[] rawData = stringsReader.ReadBytesUntil(0);
-
- if (rawData.Length == 0)
- {
- value = Utf8String.Empty;
- }
- else
- {
- // Trim off null terminator byte if its present.
- int actualLength = rawData.Length;
- if (rawData[actualLength - 1] == 0)
- actualLength--;
-
- value = new Utf8String(rawData, 0, actualLength);
- }
-
+ value = stringsReader.ReadUtf8String();
_cachedStrings[index] = value;
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs
index 27f1fbc6a..8817f38dc 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs
@@ -8,13 +8,13 @@ public enum CodedIndex
///
/// Indicates the index is an index to a member in either the TypeRef, TypeDef or TypeSpec table.
///
- TypeDefOrRef = 45,
-
+ TypeDefOrRef = ColumnType.TypeDefOrRef,
+
///
/// Indicates the index is an index to a member in either the Field, Parameter or Property table.
///
HasConstant,
-
+
///
/// Indicates the index is an index to a member in one of the following tables:
/// MethodDef, Field, TypeRef, TypeDef, Parameter, InterfaceImpl, MemberRef, Module, DeclSecurity, Property, Event,
@@ -22,57 +22,66 @@ public enum CodedIndex
/// GenericParamConstraint or MethodSpec.
///
HasCustomAttribute,
-
+
///
/// Indicates the index is an index to a member in either the Field or Parameter table.
///
HasFieldMarshal,
-
+
///
/// Indicates the index is an index to a member in either the TypeDef, MethodDef or Assembly table.
///
HasDeclSecurity,
-
+
///
/// Indicates the index is an index to a member in either the TypeDef, TypeRef, ModuleRef, MethodDef or TypeSpec
/// table.
///
MemberRefParent,
-
+
///
/// Indicates the index is an index to a member in either the Event or Property table.
///
HasSemantics,
-
+
///
/// Indicates the index is an index to a member in either the MethodDef or MemberRef table.
///
MethodDefOrRef,
-
+
///
/// Indicates the index is an index to a member in either the Field or MethodDef table.
///
MemberForwarded,
-
+
///
/// Indicates the index is an index to a member in either the File, AssemblyRef or ExportedType table.
///
Implementation,
-
+
///
- /// Indicates the index is an index to a member in either the MethodDef or MemberRef table.
+ /// Indicates the index is an index to a member in either the MethodDef or MemberRef table.
///
CustomAttributeType,
-
+
///
/// Indicates the index is an index to a member in either the Module, ModuleRef, AssemblyRef or TypeRef table.
///
ResolutionScope,
-
+
///
/// Indicates the index is an index to a member in either the TypeDef or MethodDef table.
///
TypeOrMethodDef,
+
+ ///
+ /// Indicates the index is an index to a member in one of the following tables:
+ /// MethodDef, Field, TypeRef, TypeDef, Parameter, InterfaceImpl, MemberRef, Module, DeclSecurity, Property,
+ /// Event, StandAloneSig, ModuleRef, TypeSpec, Assembly, AssemblyRef, File, ExportedType, ManifestResource,
+ /// GenericParam, GenericParamConstraint, MethodSpec, Document, LocalScope, LocalVariable, LocalConstant,
+ /// or ImportScope
+ ///
+ HasCustomDebugInformation
}
-
-}
\ No newline at end of file
+
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs
index c24876da4..5e794615d 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs
@@ -8,6 +8,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables
///
public enum ColumnType
{
+ // Normal type system indices (in sync with TableIndex).
Module = 0,
TypeRef = 1,
TypeDef = 2,
@@ -53,7 +54,18 @@ public enum ColumnType
GenericParam = 42,
MethodSpec = 43,
GenericParamConstraint = 44,
-
+
+ // PortablePDB indices (in sync with TableIndex).
+ Document = 0x30,
+ MethodDebugInformation = 0x31,
+ LocalScope = 0x32,
+ LocalVariable = 0x33,
+ LocalConstant = 0x34,
+ ImportScope = 0x35,
+ StateMachineMethod = 0x36,
+ CustomDebugInformation = 0x37,
+
+ // Coded indices (in sync with CodedIndex).
TypeDefOrRef,
HasConstant,
HasCustomAttribute,
@@ -67,13 +79,16 @@ public enum ColumnType
CustomAttributeType,
ResolutionScope,
TypeOrMethodDef,
-
- String,
+ HasCustomDebugInformation,
+
+ // Heap indices.
+ String,
Blob,
Guid,
-
+
+ // Primitives.
Byte = 0x8000001,
UInt16 = 0x8000002,
UInt32 = 0x8000004,
}
-}
\ No newline at end of file
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs
index 353a82bc3..59b6520f6 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs
@@ -37,6 +37,15 @@ IMetadataRow this[int index]
set;
}
+ ///
+ /// Gets or sets a value indicating whether the table is considered sorted.
+ ///
+ bool IsSorted
+ {
+ get;
+ set;
+ }
+
///
/// Gets the contents of a row by its row identifier.
///
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs
index 690ba9dff..de4f81473 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs
@@ -27,7 +27,7 @@ public IndexEncoder(TablesStream tableStream, params TableIndex[] tables)
_tableIndexBitCount = (int)Math.Ceiling(Math.Log(tables.Length, 2));
_tableIndexBitMask = (int)(Math.Pow(2, _tableIndexBitCount) - 1);
_maxSmallTableMemberCount = ushort.MaxValue >> _tableIndexBitCount;
-
+
}
///
@@ -37,11 +37,13 @@ public IndexSize IndexSize
{
get
{
- int maxCount = _tables
- .Select(table => _tableStream.GetTable(table).Count)
- .Max();
+ uint maxCount = 0;
+ foreach (var table in _tables)
+ maxCount = Math.Max(maxCount, _tableStream.GetTableRowCount(table));
- return maxCount > _maxSmallTableMemberCount ? IndexSize.Long : IndexSize.Short;
+ return maxCount > _maxSmallTableMemberCount
+ ? IndexSize.Long
+ : IndexSize.Short;
}
}
@@ -71,11 +73,11 @@ public MetadataToken DecodeIndex(uint codedIndex)
{
long tableIndex = codedIndex & _tableIndexBitMask;
uint rowIndex = codedIndex >> _tableIndexBitCount;
-
- return new MetadataToken(tableIndex >= _tables.Length
- ? TableIndex.Module
+
+ return new MetadataToken(tableIndex >= _tables.Length
+ ? TableIndex.Module
: _tables[tableIndex],
rowIndex);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs
index 94983d7d9..aaaea27c0 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs
@@ -25,9 +25,21 @@ public class MetadataTable : IMetadataTable, ICollection
/// The index of the table.
/// The layout of the table.
public MetadataTable(TableIndex tableIndex, TableLayout layout)
+ : this(tableIndex, layout, false)
+ {
+ }
+
+ ///
+ /// Creates a new metadata table using the provided layout.
+ ///
+ /// The index of the table.
+ /// The layout of the table.
+ /// Indicates the table is sorted or not.
+ public MetadataTable(TableIndex tableIndex, TableLayout layout, bool isSorted)
{
TableIndex = tableIndex;
Layout = layout;
+ IsSorted = isSorted;
}
///
@@ -55,6 +67,13 @@ public TRow this[int index]
set => Rows[index] = value;
}
+ ///
+ public bool IsSorted
+ {
+ get;
+ set;
+ }
+
///
IMetadataRow IMetadataTable.this[int index]
{
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs
index 56d79aa17..a28bd44fb 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs
@@ -10,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables
///
/// Represents the zero metadata token, or the absence of a metadata token.
///
- public static readonly MetadataToken Zero = new MetadataToken(0);
+ public static readonly MetadataToken Zero = new(0);
///
/// Converts a 32-bit integer to a metadata token.
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs
new file mode 100644
index 000000000..1959a9233
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Represents a single row in the Portable PDB custom debug information metadata table.
+ ///
+ public struct CustomDebugInformationRow : IMetadataRow
+ {
+ ///
+ /// Creates a new row for the Portable PDB custom debug information metadata table.
+ ///
+ ///
+ /// A coded index defining the member that this debug information is associated to.
+ ///
+ ///
+ /// An index into the GUID stream referencing the type of debug data that is stored in this record.
+ ///
+ ///
+ /// An index into the blob stream referencing the data of the record.
+ ///
+ public CustomDebugInformationRow(uint parent, uint kind, uint value)
+ {
+ Parent = parent;
+ Kind = kind;
+ Value = value;
+ }
+
+ ///
+ public TableIndex TableIndex => TableIndex.CustomDebugInformation;
+
+ ///
+ public int Count => 3;
+
+ ///
+ public uint this[int index] => index switch
+ {
+ 0 => Parent,
+ 1 => Kind,
+ 2 => Value,
+ _ => throw new IndexOutOfRangeException()
+ };
+
+ ///
+ /// Gets or sets a coded index defining the member that this debug information is associated to.
+ ///
+ public uint Parent
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the GUID stream referencing the type of debug data that is stored in this record.
+ ///
+ public uint Kind
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the blob stream referencing the data of the record.
+ ///
+ public uint Value
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Reads a single Portable PDB custom debug information row from an input stream.
+ ///
+ /// The input stream.
+ /// The layout of the custom debug information table.
+ /// The row.
+ public static CustomDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout)
+ {
+ return new CustomDebugInformationRow(
+ reader.ReadIndex((IndexSize) layout.Columns[0].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[1].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[2].Size));
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer, TableLayout layout)
+ {
+ writer.WriteIndex(Parent, (IndexSize) layout.Columns[0].Size);
+ writer.WriteIndex(Kind, (IndexSize) layout.Columns[1].Size);
+ writer.WriteIndex(Value, (IndexSize) layout.Columns[2].Size);
+ }
+
+ ///
+ /// Determines whether this row is considered equal to the provided custom debug information row.
+ ///
+ /// The other row.
+ /// true if the rows are equal, false otherwise.
+ public bool Equals(CustomDebugInformationRow other)
+ {
+ return Parent == other.Parent && Kind == other.Kind && Value == other.Value;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is CustomDebugInformationRow other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = (int) Parent;
+ hashCode = (hashCode * 397) ^ (int) Kind;
+ hashCode = (hashCode * 397) ^ (int) Value;
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString() => $"({Parent:X8}, {Kind:X8}, {Value:X8})";
+
+ ///
+ public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs
new file mode 100644
index 000000000..0d8d8378b
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Represents a single row in the Portable PDB Document metadata table.
+ ///
+ public struct DocumentRow : IMetadataRow
+ {
+ ///
+ /// Creates a new row for the Portable PDB Document metadata table.
+ ///
+ /// The index into the blob stream referencing the name of the document.
+ /// The index into the GUID stream referencing the hash algorithm identifier.
+ /// The index into the blob stream referencing the hash of the document.
+ /// The index into the GUID stream referencing the language identifier.
+ public DocumentRow(uint name, uint hashAlgorithm, uint hash, uint language)
+ {
+ Name = name;
+ HashAlgorithm = hashAlgorithm;
+ Hash = hash;
+ Language = language;
+ }
+
+ ///
+ public TableIndex TableIndex => TableIndex.Document;
+
+ ///
+ public int Count => 4;
+
+ ///
+ public uint this[int index] => index switch
+ {
+ 0 => Name,
+ 1 => HashAlgorithm,
+ 2 => Hash,
+ 3 => Language,
+ _ => throw new IndexOutOfRangeException()
+ };
+
+ ///
+ /// Gets or sets an index into the blob stream referencing the name of the document.
+ ///
+ public uint Name
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the GUID stream referencing the hash algorithm identifier.
+ ///
+ public uint HashAlgorithm
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the blob stream referencing the hash of the document.
+ ///
+ public uint Hash
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the GUID stream referencing the language identifier.
+ ///
+ public uint Language
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Reads a single Portable PDB Document row from an input stream.
+ ///
+ /// The input stream.
+ /// The layout of the document table.
+ /// The row.
+ public static DocumentRow FromReader(ref BinaryStreamReader reader, TableLayout layout)
+ {
+ return new DocumentRow(
+ reader.ReadIndex((IndexSize) layout.Columns[0].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[1].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[2].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[3].Size));
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer, TableLayout layout)
+ {
+ writer.WriteIndex(Name, (IndexSize) layout.Columns[0].Size);
+ writer.WriteIndex(HashAlgorithm, (IndexSize) layout.Columns[1].Size);
+ writer.WriteIndex(Hash, (IndexSize) layout.Columns[2].Size);
+ writer.WriteIndex(Language, (IndexSize) layout.Columns[3].Size);
+ }
+
+ ///
+ /// Determines whether this row is considered equal to the provided document row.
+ ///
+ /// The other row.
+ /// true if the rows are equal, false otherwise.
+ public bool Equals(DocumentRow other)
+ {
+ return Name == other.Name
+ && HashAlgorithm == other.HashAlgorithm
+ && Hash == other.Hash
+ && Language == other.Language;
+ }
+
+ ///
+ public override bool Equals(object? obj) => obj is DocumentRow other && Equals(other);
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = (int) Name;
+ hashCode = (hashCode * 397) ^ (int) HashAlgorithm;
+ hashCode = (hashCode * 397) ^ (int) Hash;
+ hashCode = (hashCode * 397) ^ (int) Language;
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString() => $"({Name:X8}, {HashAlgorithm:X8}, {Hash:X8}, {Language:X8})";
+
+ ///
+ public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs
index 97d7b6b92..d99f13746 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs
@@ -17,10 +17,10 @@ public struct FieldRvaRow : IMetadataRow
/// The input stream.
/// The layout of the field RVA table.
/// The row.
- public static FieldRvaRow FromReader(PEReaderContext context, ref BinaryStreamReader reader, TableLayout layout)
+ public static FieldRvaRow FromReader(MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout)
{
return new FieldRvaRow(
- context.File.GetReferenceToRva(reader.ReadUInt32()),
+ context.ReferenceFactory.GetReferenceToRva(reader.ReadUInt32()),
reader.ReadIndex((IndexSize) layout.Columns[1].Size));
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs
new file mode 100644
index 000000000..6badacd1c
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Represents a single row in the Portable PDB import scope metadata table.
+ ///
+ public struct ImportScopeRow : IMetadataRow
+ {
+ ///
+ /// Creates a new row for the Portable PDB import scope metadata table.
+ ///
+ ///
+ /// An index into the import parent scope defining the parent scope, or 0 if it is the root scope.
+ ///
+ ///
+ /// An index into the blob stream referencing the imports that this scope defines.
+ ///
+ public ImportScopeRow(uint parent, uint imports)
+ {
+ Parent = parent;
+ Imports = imports;
+ }
+
+ ///
+ public TableIndex TableIndex => TableIndex.ImportScope;
+
+ ///
+ public int Count => 2;
+
+ ///
+ public uint this[int index] => index switch
+ {
+ 0 => Parent,
+ 1 => Imports,
+ _ => throw new IndexOutOfRangeException()
+ };
+
+ ///
+ /// Gets or sets an index into the import parent scope defining the parent scope, or 0 if it is the root scope.
+ ///
+ public uint Parent
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the blob stream referencing the imports that this scope defines.
+ ///
+ public uint Imports
+ {
+ get;
+ set;
+ }
+ ///
+ /// Reads a single Portable PDB import scope row from an input stream.
+ ///
+ /// The input stream.
+ /// The layout of the import scope table.
+ /// The row.
+ public static ImportScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout)
+ {
+ return new ImportScopeRow(
+ reader.ReadIndex((IndexSize) layout.Columns[0].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[1].Size));
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer, TableLayout layout)
+ {
+ writer.WriteIndex(Parent, (IndexSize) layout.Columns[0].Size);
+ writer.WriteIndex(Imports, (IndexSize) layout.Columns[1].Size);
+ }
+
+ ///
+ /// Determines whether this row is considered equal to the provided import scope row.
+ ///
+ /// The other row.
+ /// true if the rows are equal, false otherwise.
+ public bool Equals(ImportScopeRow other)
+ {
+ return Parent == other.Parent && Imports == other.Imports;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is ImportScopeRow other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((int) Parent * 397) ^ (int) Imports;
+ }
+ }
+
+ ///
+ public override string ToString() => $"({Parent:X8}, {Imports:X8})";
+
+ ///
+ public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs
new file mode 100644
index 000000000..553195366
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Represents a single row in the Portable PDB local constant metadata table.
+ ///
+ public struct LocalConstantRow : IMetadataRow
+ {
+ ///
+ /// Creates a new row for the Portable PDB Local Constant metadata table.
+ ///
+ /// An index into the strings stream referencing the name of the constant.
+ /// An index into the blob stream referencing the signature of the constant.
+ public LocalConstantRow(uint name, uint signature)
+ {
+ Name = name;
+ Signature = signature;
+ }
+
+ ///
+ public TableIndex TableIndex => TableIndex.LocalConstant;
+
+ ///
+ public int Count => 2;
+
+ ///
+ public uint this[int index] => index switch
+ {
+ 0 => Name,
+ 1 => Signature,
+ _ => throw new IndexOutOfRangeException()
+ };
+
+ ///
+ /// Gets or sets an index into the strings stream referencing the name of the constant.
+ ///
+ public uint Name
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the blob stream referencing the signature of the constant.
+ ///
+ public uint Signature
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Reads a single Portable PDB local constant row from an input stream.
+ ///
+ /// The input stream.
+ /// The layout of the local constant table.
+ /// The row.
+ public static LocalConstantRow FromReader(ref BinaryStreamReader reader, TableLayout layout)
+ {
+ return new LocalConstantRow(
+ reader.ReadIndex((IndexSize) layout.Columns[0].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[1].Size));
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer, TableLayout layout)
+ {
+ writer.WriteIndex(Name, (IndexSize) layout.Columns[0].Size);
+ writer.WriteIndex(Signature, (IndexSize) layout.Columns[1].Size);
+ }
+
+ ///
+ /// Determines whether this row is considered equal to the provided local constant row.
+ ///
+ /// The other row.
+ /// true if the rows are equal, false otherwise.
+ public bool Equals(LocalConstantRow other)
+ {
+ return Name == other.Name && Signature == other.Signature;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is LocalConstantRow other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((int) Name * 397) ^ (int) Signature;
+ }
+ }
+
+ ///
+ public override string ToString() => $"({Name:X8}, {Signature:X8})";
+
+ ///
+ public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs
new file mode 100644
index 000000000..9fa9c387e
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Represents a single row in the Portable PDB local scope metadata table.
+ ///
+ public struct LocalScopeRow : IMetadataRow
+ {
+ ///
+ /// Creates a new row for the Portable PDB Local Scope metadata table.
+ ///
+ /// An index into the method table that defines the scope.
+ /// An index into the import scope table that defines the scope.
+ /// An index into the local variable table referencing the first local variable in the method.
+ /// An index into the local constant table referencing the first constant in the method.
+ /// The starting CIL offset of the scope.
+ /// The number of CIL bytes the scope spans.
+ public LocalScopeRow(uint method, uint importScope, uint variableList, uint constantList, uint startOffset, uint length)
+ {
+ Method = method;
+ ImportScope = importScope;
+ VariableList = variableList;
+ ConstantList = constantList;
+ StartOffset = startOffset;
+ Length = length;
+ }
+
+ ///
+ public TableIndex TableIndex => TableIndex.Document;
+
+ ///
+ public int Count => 6;
+
+ ///
+ public uint this[int index] => index switch
+ {
+ 0 => Method,
+ 1 => ImportScope,
+ 2 => VariableList,
+ 3 => ConstantList,
+ 4 => StartOffset,
+ 5 => Length,
+ _ => throw new IndexOutOfRangeException()
+ };
+
+ ///
+ /// Gets or sets an index into the method table that defines the scope.
+ ///
+ public uint Method
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the import scope table that defines the scope.
+ ///
+ public uint ImportScope
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the local variable table referencing the first local variable in the method.
+ ///
+ public uint VariableList
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the local constant table referencing the first constant in the method.
+ ///
+ public uint ConstantList
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets The starting CIL offset of the scope.
+ ///
+ public uint StartOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of CIL bytes the scope spans.
+ ///
+ public uint Length
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Reads a single Portable PDB local scope row from an input stream.
+ ///
+ /// The input stream.
+ /// The layout of the local socpe table.
+ /// The row.
+ public static LocalScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout)
+ {
+ return new LocalScopeRow(
+ reader.ReadIndex((IndexSize) layout.Columns[0].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[1].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[2].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[3].Size),
+ reader.ReadUInt32(),
+ reader.ReadUInt32());
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer, TableLayout layout)
+ {
+ writer.WriteIndex(Method, (IndexSize) layout.Columns[0].Size);
+ writer.WriteIndex(ImportScope, (IndexSize) layout.Columns[1].Size);
+ writer.WriteIndex(VariableList, (IndexSize) layout.Columns[2].Size);
+ writer.WriteIndex(ConstantList, (IndexSize) layout.Columns[3].Size);
+ writer.WriteUInt32(StartOffset);
+ writer.WriteUInt32(Length);
+ }
+
+ ///
+ /// Determines whether this row is considered equal to the provided local scope row.
+ ///
+ /// The other row.
+ /// true if the rows are equal, false otherwise.
+ public bool Equals(LocalScopeRow other)
+ {
+ return Method == other.Method
+ && ImportScope == other.ImportScope
+ && VariableList == other.VariableList
+ && ConstantList == other.ConstantList
+ && StartOffset == other.StartOffset
+ && Length == other.Length;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is LocalScopeRow other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = (int) Method;
+ hashCode = (hashCode * 397) ^ (int) ImportScope;
+ hashCode = (hashCode * 397) ^ (int) VariableList;
+ hashCode = (hashCode * 397) ^ (int) ConstantList;
+ hashCode = (hashCode * 397) ^ (int) StartOffset;
+ hashCode = (hashCode * 397) ^ (int) Length;
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"({Method:X8}, {ImportScope:X8}, {VariableList:X8}, {ConstantList:X8}, {StartOffset:X8}, {Length:X8})";
+ }
+
+ ///
+ public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs
new file mode 100644
index 000000000..503f2f555
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Provides members defining all possible flags that can be assigned to a local variable.
+ ///
+ [Flags]
+ public enum LocalVariableAttributes : ushort
+ {
+ ///
+ /// Indicates the local variable should be hidden in a debugger view.
+ ///
+ DebuggerHidden = 0x0001
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs
new file mode 100644
index 000000000..1c27f04f0
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Represents a single row in the Portable PDB local variable metadata table.
+ ///
+ public struct LocalVariableRow : IMetadataRow
+ {
+ ///
+ /// Creates a new row for the Portable PDB Local Variable metadata table.
+ ///
+ /// The attributes associated to the local variable.
+ /// The index of the local variable.
+ /// An index into the strings stream referencing the name of the local variable.
+ public LocalVariableRow(LocalVariableAttributes attributes, ushort index, uint name)
+ {
+ Attributes = attributes;
+ Index = index;
+ Name = name;
+ }
+
+ ///
+ public TableIndex TableIndex => TableIndex.LocalVariable;
+
+ ///
+ public int Count => 3;
+
+ ///
+ public uint this[int index] => index switch
+ {
+ 0 => (uint) Attributes,
+ 1 => Index,
+ 2 => Name,
+ _ => throw new IndexOutOfRangeException()
+ };
+
+ ///
+ /// Gets or sets the attributes associated to the local variable.
+ ///
+ public LocalVariableAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the index of the local variable.
+ ///
+ public ushort Index
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the strings stream referencing the name of the local variable.
+ ///
+ public uint Name
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Reads a single Portable PDB local variable row from an input stream.
+ ///
+ /// The input stream.
+ /// The layout of the local variable table.
+ /// The row.
+ public static LocalVariableRow FromReader(ref BinaryStreamReader reader, TableLayout layout)
+ {
+ return new LocalVariableRow(
+ (LocalVariableAttributes) reader.ReadUInt16(),
+ reader.ReadUInt16(),
+ reader.ReadIndex((IndexSize) layout.Columns[2].Size));
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer, TableLayout layout)
+ {
+ writer.WriteUInt16((ushort) Attributes);
+ writer.WriteUInt16(Index);
+ writer.WriteIndex(Name, (IndexSize) layout.Columns[2].Size);
+ }
+
+ ///
+ /// Determines whether this row is considered equal to the provided local variable row.
+ ///
+ /// The other row.
+ /// true if the rows are equal, false otherwise.
+ public bool Equals(LocalVariableRow other)
+ {
+ return Attributes == other.Attributes
+ && Index == other.Index
+ && Name == other.Name;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is LocalScopeRow other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = (int) Attributes;
+ hashCode = (hashCode * 397) ^ Index;
+ hashCode = (hashCode * 397) ^ (int) Name;
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString() => $"({(ushort) Attributes:X4}, {Index:X4}, {Name:X8})";
+
+ ///
+ public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs
new file mode 100644
index 000000000..9cfbb821b
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Represents a single row in the Portable PDB Document metadata table.
+ ///
+ public struct MethodDebugInformationRow : IMetadataRow
+ {
+ ///
+ /// Creates a new row for the Portable PDB Method Debug Information metadata table.
+ ///
+ ///
+ /// The index into the Document table referencing the document that declares the method.
+ ///
+ ///
+ /// The index into the blob stream referencing an array of sequence points that make up the method.
+ ///
+ public MethodDebugInformationRow(uint document, uint sequencePoints)
+ {
+ Document = document;
+ SequencePoints = sequencePoints;
+ }
+
+ ///
+ public TableIndex TableIndex => TableIndex.Document;
+
+ ///
+ public int Count => 2;
+
+ ///
+ public uint this[int index] => index switch
+ {
+ 0 => Document,
+ 1 => SequencePoints,
+ _ => throw new IndexOutOfRangeException()
+ };
+
+ ///
+ /// Gets or sets an index into the Document table referencing the document that declares the method, or 0
+ /// if the method does not have sequence points or spans multiple documents.
+ ///
+ public uint Document
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the blob stream referencing an array of sequence points that make up the method,
+ /// or 0 if no sequence points are available.
+ ///
+ public uint SequencePoints
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Reads a single Portable PDB Method Debug Information row from an input stream.
+ ///
+ /// The input stream.
+ /// The layout of the method debug information table.
+ /// The row.
+ public static MethodDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout)
+ {
+ return new MethodDebugInformationRow(
+ reader.ReadIndex((IndexSize) layout.Columns[0].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[1].Size));
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer, TableLayout layout)
+ {
+ writer.WriteIndex(Document, (IndexSize) layout.Columns[0].Size);
+ writer.WriteIndex(SequencePoints, (IndexSize) layout.Columns[1].Size);
+ }
+
+ ///
+ /// Determines whether this row is considered equal to the provided method debug information row.
+ ///
+ /// The other row.
+ /// true if the rows are equal, false otherwise.
+ public bool Equals(MethodDebugInformationRow other)
+ {
+ return Document == other.Document && SequencePoints == other.SequencePoints;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is MethodDebugInformationRow other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((int) Document * 397) ^ (int) SequencePoints;
+ }
+ }
+
+ ///
+ public override string ToString() => $"({Document:X8}, {SequencePoints:X8})";
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return new MetadataRowColumnEnumerator(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs
index 2b6df546f..6cd6e5481 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs
@@ -17,10 +17,10 @@ public struct MethodDefinitionRow : IMetadataRow
/// The input stream.
/// The layout of the method definition table.
/// The row.
- public static MethodDefinitionRow FromReader(PEReaderContext context, ref BinaryStreamReader reader, TableLayout layout)
+ public static MethodDefinitionRow FromReader(MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout)
{
return new MethodDefinitionRow(
- context.File.GetReferenceToRva(reader.ReadUInt32()),
+ context.ReferenceFactory.GetReferenceToRva(reader.ReadUInt32()),
(MethodImplAttributes) reader.ReadUInt16(),
(MethodAttributes) reader.ReadUInt16(),
reader.ReadIndex((IndexSize) layout.Columns[3].Size),
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs
new file mode 100644
index 000000000..914125195
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows
+{
+ ///
+ /// Represents a single row in the Portable PDB state machine method metadata table.
+ ///
+ public struct StateMachineMethodRow : IMetadataRow
+ {
+ ///
+ /// Creates a new row for the Portable PDB state machine method metadata table.
+ ///
+ ///
+ /// An index into the method table referencing the MoveNext method of an async state machine.
+ ///
+ ///
+ /// An index into the method table referencing the kickoff method of an async state machine.
+ ///
+ public StateMachineMethodRow(uint moveNextMethod, uint kickoffMethod)
+ {
+ MoveNextMethod = moveNextMethod;
+ KickoffMethod = kickoffMethod;
+ }
+
+ ///
+ public TableIndex TableIndex => TableIndex.StateMachineMethod;
+
+ ///
+ public int Count => 2;
+
+ ///
+ public uint this[int index] => index switch
+ {
+ 0 => MoveNextMethod,
+ 1 => KickoffMethod,
+ _ => throw new IndexOutOfRangeException()
+ };
+
+ ///
+ /// Gets or sets an index into the method table referencing the MoveNext method of an async state machine.
+ ///
+ public uint MoveNextMethod
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets an index into the method table referencing the kickoff method of an async state machine.
+ ///
+ public uint KickoffMethod
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Reads a single Portable PDB state machine method row from an input stream.
+ ///
+ /// The input stream.
+ /// The layout of the state machine method table.
+ /// The row.
+ public static StateMachineMethodRow FromReader(ref BinaryStreamReader reader, TableLayout layout)
+ {
+ return new StateMachineMethodRow(
+ reader.ReadIndex((IndexSize) layout.Columns[0].Size),
+ reader.ReadIndex((IndexSize) layout.Columns[1].Size));
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer, TableLayout layout)
+ {
+ writer.WriteIndex(MoveNextMethod, (IndexSize) layout.Columns[0].Size);
+ writer.WriteIndex(KickoffMethod, (IndexSize) layout.Columns[1].Size);
+ }
+
+ ///
+ /// Determines whether this row is considered equal to the provided state machine method row.
+ ///
+ /// The other row.
+ /// true if the rows are equal, false otherwise.
+ public bool Equals(StateMachineMethodRow other)
+ {
+ return MoveNextMethod == other.MoveNextMethod && KickoffMethod == other.KickoffMethod;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is StateMachineMethodRow other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((int) MoveNextMethod * 397) ^ (int) KickoffMethod;
+ }
+ }
+
+ ///
+ public override string ToString() => $"({MoveNextMethod:X8}, {KickoffMethod:X8})";
+
+ ///
+ public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs
index 0529b40f3..b39575f70 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs
@@ -27,7 +27,7 @@ public class SerializedMetadataTable : MetadataTable
/// The input stream.
/// The layout of the table.
public delegate TRow ReadRowExtendedDelegate(
- PEReaderContext context,
+ MetadataReaderContext context,
ref BinaryStreamReader reader,
TableLayout layout);
@@ -42,9 +42,15 @@ public delegate TRow ReadRowExtendedDelegate(
/// The input stream.
/// The index of the table.
/// The layout of the table.
+ /// Indicates the table is sorted or not.
/// The method to use for reading each row in the table.
- public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableIndex, TableLayout originalLayout, ReadRowDelegate readRow)
- : base(tableIndex, originalLayout)
+ public SerializedMetadataTable(
+ in BinaryStreamReader reader,
+ TableIndex tableIndex,
+ TableLayout originalLayout,
+ bool isSorted,
+ ReadRowDelegate readRow)
+ : base(tableIndex, originalLayout, isSorted)
{
_reader = reader;
_originalLayout = originalLayout;
@@ -59,14 +65,16 @@ public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableInd
/// The input stream.
/// The index of the table.
/// The layout of the table.
+ /// Indicates the table is sorted or not.
/// The method to use for reading each row in the table.
public SerializedMetadataTable(
- PEReaderContext context,
+ MetadataReaderContext context,
in BinaryStreamReader reader,
TableIndex tableIndex,
TableLayout originalLayout,
+ bool isSorted,
ReadRowExtendedDelegate readRow)
- : this(reader, tableIndex, originalLayout,
+ : this(reader, tableIndex, originalLayout, isSorted,
(ref BinaryStreamReader r, TableLayout l) => readRow(context, ref r, l))
{
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs
index f2a910eb8..8608952fc 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using AsmResolver.IO;
+using AsmResolver.PE.DotNet.Metadata.Pdb;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
namespace AsmResolver.PE.DotNet.Metadata.Tables
@@ -9,24 +10,34 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables
///
/// Provides an implementation of a tables stream that obtains tables from a readable segment in a file.
///
- public class SerializedTableStream : TablesStream
+ public class SerializedTableStream : TablesStream, ILazyMetadataStream
{
- private readonly PEReaderContext _context;
+ private readonly MetadataReaderContext _context;
private readonly BinaryStreamReader _reader;
private readonly ulong _validMask;
private readonly ulong _sortedMask;
private readonly uint[] _rowCounts;
- private readonly IndexSize[] _indexSizes;
private readonly uint _headerSize;
private bool _tablesInitialized;
+ ///
+ /// Same as but may contain row counts from an external tables stream.
+ /// This is required for metadata directories containing Portable PDB debug data.
+ ///
+ private uint[]? _combinedRowCounts;
+
+ ///
+ /// Contains the initial sizes of every column type.
+ ///
+ private IndexSize[]? _indexSizes;
+
///
/// Creates a new tables stream with the provided byte array as the raw contents of the stream.
///
/// The reader context.
/// The name of the stream.
/// The raw contents of the stream.
- public SerializedTableStream(PEReaderContext context, string name, byte[] rawData)
+ public SerializedTableStream(MetadataReaderContext context, string name, byte[] rawData)
: this(context, name, ByteArrayDataSource.CreateReader(rawData))
{
}
@@ -37,7 +48,7 @@ public SerializedTableStream(PEReaderContext context, string name, byte[] rawDat
/// The reader context.
/// The name of the stream.
/// The raw contents of the stream.
- public SerializedTableStream(PEReaderContext context, string name, in BinaryStreamReader reader)
+ public SerializedTableStream(MetadataReaderContext context, string name, in BinaryStreamReader reader)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
_context = context ?? throw new ArgumentNullException(nameof(context));
@@ -54,15 +65,12 @@ public SerializedTableStream(PEReaderContext context, string name, in BinaryStre
Log2LargestRid = headerReader.ReadByte();
_validMask = headerReader.ReadUInt64();
_sortedMask = headerReader.ReadUInt64();
-
_rowCounts = ReadRowCounts(ref headerReader);
if (HasExtraData)
ExtraData = headerReader.ReadUInt32();
_headerSize = headerReader.RelativeOffset;
-
- _indexSizes = InitializeIndexSizes();
}
///
@@ -73,19 +81,55 @@ public SerializedTableStream(PEReaderContext context, string name, in BinaryStre
private uint[] ReadRowCounts(ref BinaryStreamReader reader)
{
- const TableIndex maxTableIndex = TableIndex.GenericParamConstraint;
+ uint[] result = new uint[(int) TableIndex.Max];
- var result = new uint[(int) maxTableIndex + 1 ];
- for (TableIndex i = 0; i <= maxTableIndex; i++)
- result[(int) i] = HasTable(_validMask, i) ? reader.ReadUInt32() : 0;
+ for (TableIndex i = 0; i < TableIndex.Max; i++)
+ {
+ result[(int) i] = HasTable(_validMask, i)
+ ? reader.ReadUInt32()
+ : 0;
+ }
return result;
}
+ ///
+ public void Initialize(IMetadata parentMetadata)
+ {
+ if (parentMetadata.TryGetStream(out PdbStream? pdbStream))
+ {
+ // Metadata that contains a PDB stream should use the row counts provided in the pdb stream
+ // for computing the size of a column.
+ _combinedRowCounts = new uint[_rowCounts.Length];
+ ExternalRowCounts = new uint[(int) TableIndex.Document];
+
+ for (int i = 0; i < (int) TableIndex.Document; i++)
+ {
+ _combinedRowCounts[i] = pdbStream.TypeSystemRowCounts[i];
+ ExternalRowCounts[i] = pdbStream.TypeSystemRowCounts[i];
+ }
+
+ for (int i = (int) TableIndex.Document; i < (int) TableIndex.Max; i++)
+ _combinedRowCounts[i] = _rowCounts[i];
+
+ }
+ else
+ {
+ // Otherwise, just use the original row counts array.
+ _combinedRowCounts = _rowCounts;
+ }
+
+ _indexSizes = InitializeIndexSizes();
+ }
+
///
protected override uint GetColumnSize(ColumnType columnType)
{
- if (_tablesInitialized || (int) columnType >= _indexSizes.Length)
+ if (_tablesInitialized)
+ return base.GetColumnSize(columnType);
+ if (_indexSizes is null)
+ throw new InvalidOperationException("Serialized tables stream is not fully initialized yet.");
+ if ((int) columnType >= _indexSizes.Length)
return base.GetColumnSize(columnType);
return (uint) _indexSizes[(int) columnType];
}
@@ -149,6 +193,15 @@ private IndexSize[] InitializeIndexSizes()
// TypeOrMethodDef
GetCodedIndexSize(TableIndex.TypeDef, TableIndex.Method),
+
+ // HasCustomDebugInformation
+ GetCodedIndexSize(TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef,
+ TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module,
+ TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig,
+ TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef,
+ TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam,
+ TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document,
+ TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope)
});
return result.ToArray();
@@ -156,19 +209,22 @@ private IndexSize[] InitializeIndexSizes()
private IndexSize GetCodedIndexSize(params TableIndex[] tables)
{
+ if (_combinedRowCounts is null)
+ throw new InvalidOperationException("Serialized tables stream is not fully initialized yet.");
+
int tableIndexBitCount = (int) Math.Ceiling(Math.Log(tables.Length, 2));
int maxSmallTableMemberCount = ushort.MaxValue >> tableIndexBitCount;
- return tables.Select(t => _rowCounts[(int) t]).All(c => c < maxSmallTableMemberCount)
+ return tables.Select(t => _combinedRowCounts[(int) t]).All(c => c < maxSmallTableMemberCount)
? IndexSize.Short
: IndexSize.Long;
}
///
- protected override IList GetTables()
+ protected override IList GetTables()
{
uint offset = _headerSize;
- var tables = new IMetadataTable[]
+ var tables = new IMetadataTable?[]
{
CreateNextTable(TableIndex.Module, ref offset, ModuleDefinitionRow.FromReader),
CreateNextTable(TableIndex.TypeRef, ref offset, TypeReferenceRow.FromReader),
@@ -215,6 +271,17 @@ protected override IList GetTables()
CreateNextTable(TableIndex.GenericParam, ref offset, GenericParameterRow.FromReader),
CreateNextTable(TableIndex.MethodSpec, ref offset, MethodSpecificationRow.FromReader),
CreateNextTable(TableIndex.GenericParamConstraint, ref offset, GenericParameterConstraintRow.FromReader),
+ null,
+ null,
+ null,
+ CreateNextTable(TableIndex.Document, ref offset, DocumentRow.FromReader),
+ CreateNextTable(TableIndex.MethodDebugInformation, ref offset, MethodDebugInformationRow.FromReader),
+ CreateNextTable(TableIndex.LocalScope, ref offset, LocalScopeRow.FromReader),
+ CreateNextTable(TableIndex.LocalVariable, ref offset, LocalVariableRow.FromReader),
+ CreateNextTable(TableIndex.LocalConstant, ref offset, LocalConstantRow.FromReader),
+ CreateNextTable(TableIndex.ImportScope, ref offset, ImportScopeRow.FromReader),
+ CreateNextTable(TableIndex.StateMachineMethod, ref offset, StateMachineMethodRow.FromReader),
+ CreateNextTable(TableIndex.CustomDebugInformation, ref offset, CustomDebugInformationRow.FromReader),
};
_tablesInitialized = true;
return tables;
@@ -239,6 +306,7 @@ private SerializedMetadataTable CreateNextTable(
CreateNextRawTableReader(index, ref offset),
index,
TableLayouts[(int) index],
+ IsSorted(_sortedMask, index),
readRow);
}
@@ -253,6 +321,7 @@ private SerializedMetadataTable CreateNextTable(
CreateNextRawTableReader(index, ref offset),
index,
TableLayouts[(int) index],
+ IsSorted(_sortedMask, index),
readRow);
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs
index d9ca9e3bc..8ee7dd66b 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs
@@ -53,8 +53,21 @@ public enum TableIndex : byte
GenericParam = 42,
MethodSpec = 43,
GenericParamConstraint = 44,
- Max = GenericParamConstraint + 1,
+
+ MaxTypeSystemTableIndex = GenericParamConstraint,
+
+ Document = 0x30,
+ MethodDebugInformation = 0x31,
+ LocalScope = 0x32,
+ LocalVariable = 0x33,
+ LocalConstant = 0x34,
+ ImportScope = 0x35,
+ StateMachineMethod = 0x36,
+ CustomDebugInformation = 0x37,
+
+ Max = CustomDebugInformation + 1,
String = 0x70
}
+
}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs
new file mode 100644
index 000000000..e9573dfb2
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs
@@ -0,0 +1,19 @@
+namespace AsmResolver.PE.DotNet.Metadata.Tables;
+
+///
+/// Provides extension methods to the enumeration.
+///
+public static class TableIndexExtensions
+{
+ ///
+ /// Determines whether the provided index is a valid table index.
+ ///
+ /// The index.
+ /// true if valid, false otherwise.
+ public static bool IsValidTableIndex(this TableIndex index)
+ {
+ return index is >= TableIndex.Module and <= TableIndex.GenericParamConstraint
+ or >= TableIndex.Document and <= TableIndex.CustomDebugInformation
+ or TableIndex.String;
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs
new file mode 100644
index 000000000..877d34129
--- /dev/null
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs
@@ -0,0 +1,474 @@
+using System.Collections.Generic;
+using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
+
+namespace AsmResolver.PE.DotNet.Metadata.Tables
+{
+ public partial class TablesStream
+ {
+ ///
+ /// Obtains the collection of tables in the tables stream.
+ ///
+ /// The tables, including empty tables if there are any.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetTables()
+ {
+ var layouts = TableLayouts;
+ return new IMetadataTable?[]
+ {
+ new MetadataTable(TableIndex.Module, layouts[0]),
+ new MetadataTable(TableIndex.TypeRef, layouts[1]),
+ new MetadataTable(TableIndex.TypeDef, layouts[2]),
+ new MetadataTable(TableIndex.FieldPtr, layouts[3]),
+ new MetadataTable(TableIndex.Field, layouts[4]),
+ new MetadataTable(TableIndex.Method, layouts[5]),
+ new MetadataTable(TableIndex.Method, layouts[6]),
+ new MetadataTable(TableIndex.ParamPtr, layouts[7]),
+ new MetadataTable(TableIndex.Param, layouts[8]),
+ new MetadataTable(TableIndex.InterfaceImpl, layouts[9], true),
+ new MetadataTable(TableIndex.MemberRef, layouts[10]),
+ new MetadataTable(TableIndex.Constant, layouts[11], true),
+ new MetadataTable(TableIndex.CustomAttribute, layouts[12], true),
+ new MetadataTable(TableIndex.FieldMarshal, layouts[13], true),
+ new MetadataTable(TableIndex.DeclSecurity, layouts[14], true),
+ new MetadataTable(TableIndex.ClassLayout, layouts[15], true),
+ new MetadataTable(TableIndex.FieldLayout, layouts[16], true),
+ new MetadataTable(TableIndex.StandAloneSig, layouts[17]),
+ new MetadataTable(TableIndex.EventMap, layouts[18]),
+ new MetadataTable(TableIndex.EventPtr, layouts[19]),
+ new MetadataTable(TableIndex.Event, layouts[20]),
+ new MetadataTable(TableIndex.PropertyMap, layouts[21]),
+ new MetadataTable(TableIndex.PropertyPtr, layouts[22]),
+ new MetadataTable(TableIndex.Property, layouts[23]),
+ new MetadataTable(TableIndex.MethodSemantics, layouts[24], true),
+ new MetadataTable(TableIndex.MethodImpl, layouts[25], true),
+ new MetadataTable(TableIndex.ModuleRef, layouts[26]),
+ new MetadataTable(TableIndex.TypeSpec, layouts[27]),
+ new MetadataTable(TableIndex.ImplMap, layouts[28], true),
+ new MetadataTable(TableIndex.FieldRva, layouts[29], true),
+ new MetadataTable(TableIndex.EncLog, layouts[30]),
+ new MetadataTable(TableIndex.EncMap, layouts[31]),
+ new MetadataTable(TableIndex.Assembly, layouts[32]),
+ new MetadataTable(TableIndex.AssemblyProcessor, layouts[33]),
+ new MetadataTable(TableIndex.AssemblyOS, layouts[34]),
+ new MetadataTable(TableIndex.AssemblyRef, layouts[35]),
+ new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[36]),
+ new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[37]),
+ new MetadataTable(TableIndex.File, layouts[38]),
+ new MetadataTable(TableIndex.ExportedType, layouts[39]),
+ new MetadataTable(TableIndex.ManifestResource, layouts[40]),
+ new MetadataTable(TableIndex.NestedClass, layouts[41], true),
+ new MetadataTable(TableIndex.GenericParam, layouts[42], true),
+ new MetadataTable(TableIndex.MethodSpec, layouts[43]),
+ new MetadataTable(TableIndex.GenericParamConstraint, layouts[44], true),
+ null,
+ null,
+ null,
+ new MetadataTable(TableIndex.Document, layouts[48]),
+ new MetadataTable(TableIndex.MethodDebugInformation, layouts[49]),
+ new MetadataTable(TableIndex.LocalScope, layouts[50], true),
+ new MetadataTable(TableIndex.LocalVariable, layouts[51]),
+ new MetadataTable(TableIndex.LocalConstant, layouts[52]),
+ new MetadataTable(TableIndex.ImportScope, layouts[53]),
+ new MetadataTable(TableIndex.StateMachineMethod, layouts[54]),
+ new MetadataTable(TableIndex.CustomDebugInformation, layouts[55], true),
+ };
+ }
+
+ private Dictionary CreateIndexEncoders()
+ {
+ return new()
+ {
+ [CodedIndex.TypeDefOrRef] = new IndexEncoder(this,
+ TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec),
+
+ [CodedIndex.HasConstant] = new(this,
+ TableIndex.Field, TableIndex.Param, TableIndex.Property),
+
+ [CodedIndex.HasCustomAttribute] = new(this,
+ TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef,
+ TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module,
+ TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig,
+ TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef,
+ TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam,
+ TableIndex.GenericParamConstraint, TableIndex.MethodSpec),
+
+ [CodedIndex.HasFieldMarshal] = new(this,
+ TableIndex.Field, TableIndex.Param),
+
+ [CodedIndex.HasDeclSecurity] = new(this,
+ TableIndex.TypeDef, TableIndex.Method, TableIndex.Assembly),
+
+ [CodedIndex.MemberRefParent] = new(this,
+ TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef, TableIndex.Method,
+ TableIndex.TypeSpec),
+
+ [CodedIndex.HasSemantics] = new(this,
+ TableIndex.Event, TableIndex.Property),
+
+ [CodedIndex.MethodDefOrRef] = new(this,
+ TableIndex.Method, TableIndex.MemberRef),
+
+ [CodedIndex.MemberForwarded] = new(this,
+ TableIndex.Field, TableIndex.Method),
+
+ [CodedIndex.Implementation] = new(this,
+ TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType),
+
+ [CodedIndex.CustomAttributeType] = new(this,
+ 0, 0, TableIndex.Method, TableIndex.MemberRef, 0),
+
+ [CodedIndex.ResolutionScope] = new(this,
+ TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef),
+
+ [CodedIndex.TypeOrMethodDef] = new(this,
+ TableIndex.TypeDef, TableIndex.Method),
+
+ [CodedIndex.HasCustomDebugInformation] = new(this,
+ TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, TableIndex.Param,
+ TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, TableIndex.DeclSecurity,
+ TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, TableIndex.ModuleRef,
+ TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, TableIndex.File,
+ TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam,
+ TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document,
+ TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope)
+ };
+ }
+
+ ///
+ /// Gets an ordered collection of the current table layouts.
+ ///
+ /// The table layouts.
+ protected TableLayout[] GetTableLayouts() => new[]
+ {
+ // Module
+ new TableLayout(
+ new ColumnLayout("Generation", ColumnType.UInt16),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Mvid", ColumnType.Guid, GuidIndexSize),
+ new ColumnLayout("EncId", ColumnType.Guid, GuidIndexSize),
+ new ColumnLayout("EncBaseId", ColumnType.Guid, GuidIndexSize)),
+
+ // TypeRef
+ new TableLayout(
+ new ColumnLayout("ResolutionScope", ColumnType.ResolutionScope,
+ GetColumnSize(ColumnType.ResolutionScope)),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Namespace", ColumnType.Guid, StringIndexSize)),
+
+ // TypeDef
+ new TableLayout(
+ new ColumnLayout("Flags", ColumnType.UInt32),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Namespace", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Extends", ColumnType.TypeDefOrRef,
+ GetColumnSize(ColumnType.TypeDefOrRef)),
+ new ColumnLayout("FieldList", ColumnType.Field, GetColumnSize(ColumnType.Field)),
+ new ColumnLayout("MethodList", ColumnType.Method, GetColumnSize(ColumnType.Method))),
+
+ // FieldPtr
+ new TableLayout(
+ new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))),
+
+ // Field
+ new TableLayout(
+ new ColumnLayout("Flags", ColumnType.UInt16),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
+
+ // MethodPtr
+ new TableLayout(
+ new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method))),
+
+ // Method
+ new TableLayout(
+ new ColumnLayout("RVA", ColumnType.UInt32),
+ new ColumnLayout("ImplFlags", ColumnType.UInt16),
+ new ColumnLayout("Flags", ColumnType.UInt16),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize),
+ new ColumnLayout("ParamList", ColumnType.Param, GetColumnSize(ColumnType.Param))),
+
+ // ParamPtr
+ new TableLayout(
+ new ColumnLayout("Parameter", ColumnType.Param, GetColumnSize(ColumnType.Param))),
+
+ // Parameter
+ new TableLayout(
+ new ColumnLayout("Flags", ColumnType.UInt16),
+ new ColumnLayout("Sequence", ColumnType.UInt16),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize)),
+
+ // InterfaceImpl
+ new TableLayout(
+ new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
+ new ColumnLayout("Interface", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))),
+
+ // MemberRef
+ new TableLayout(
+ new ColumnLayout("Parent", ColumnType.MemberRefParent, GetColumnSize(ColumnType.MemberRefParent)),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
+
+ // Constant
+ new TableLayout(
+ new ColumnLayout("Type", ColumnType.Byte),
+ new ColumnLayout("Padding", ColumnType.Byte),
+ new ColumnLayout("Parent", ColumnType.HasConstant, GetColumnSize(ColumnType.HasConstant)),
+ new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)),
+
+ // CustomAttribute
+ new TableLayout(
+ new ColumnLayout("Parent", ColumnType.HasCustomAttribute,
+ GetColumnSize(ColumnType.HasCustomAttribute)),
+ new ColumnLayout("Type", ColumnType.CustomAttributeType,
+ GetColumnSize(ColumnType.CustomAttributeType)),
+ new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)),
+
+ // FieldMarshal
+ new TableLayout(
+ new ColumnLayout("Parent", ColumnType.HasFieldMarshal, GetColumnSize(ColumnType.HasFieldMarshal)),
+ new ColumnLayout("NativeType", ColumnType.Blob, BlobIndexSize)),
+
+ // DeclSecurity
+ new TableLayout(
+ new ColumnLayout("Action", ColumnType.UInt16),
+ new ColumnLayout("Parent", ColumnType.HasDeclSecurity, GetColumnSize(ColumnType.HasDeclSecurity)),
+ new ColumnLayout("PermissionSet", ColumnType.Blob, BlobIndexSize)),
+
+ // ClassLayout
+ new TableLayout(
+ new ColumnLayout("PackingSize", ColumnType.UInt16),
+ new ColumnLayout("ClassSize", ColumnType.UInt32),
+ new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))),
+
+ // FieldLayout
+ new TableLayout(
+ new ColumnLayout("Offset", ColumnType.UInt32),
+ new ColumnLayout("Field", ColumnType.TypeDef, GetColumnSize(ColumnType.Field))),
+
+ // StandAloneSig
+ new TableLayout(
+ new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
+
+ // EventMap
+ new TableLayout(
+ new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
+ new ColumnLayout("EventList", ColumnType.Event, GetColumnSize(ColumnType.Event))),
+
+ // EventPtr
+ new TableLayout(
+ new ColumnLayout("Event", ColumnType.Event, GetColumnSize(ColumnType.Event))),
+
+ // Event
+ new TableLayout(
+ new ColumnLayout("Flags", ColumnType.UInt16),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("EventType", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))),
+
+ // PropertyMap
+ new TableLayout(
+ new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
+ new ColumnLayout("PropertyList", ColumnType.Event, GetColumnSize(ColumnType.Property))),
+
+ // PropertyPtr
+ new TableLayout(
+ new ColumnLayout("Property", ColumnType.Property, GetColumnSize(ColumnType.Property))),
+
+ // Property
+ new TableLayout(
+ new ColumnLayout("Flags", ColumnType.UInt16),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("PropertyType", ColumnType.Blob, BlobIndexSize)),
+
+ // MethodSemantics
+ new TableLayout(
+ new ColumnLayout("Semantic", ColumnType.UInt16),
+ new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)),
+ new ColumnLayout("Association", ColumnType.HasSemantics, GetColumnSize(ColumnType.HasSemantics))),
+
+ // MethodImpl
+ new TableLayout(
+ new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
+ new ColumnLayout("MethodBody", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef)),
+ new ColumnLayout("MethodDeclaration", ColumnType.MethodDefOrRef,
+ GetColumnSize(ColumnType.MethodDefOrRef))),
+
+ // ModuleRef
+ new TableLayout(
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize)),
+
+ // TypeSpec
+ new TableLayout(
+ new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
+
+ // ImplMap
+ new TableLayout(
+ new ColumnLayout("MappingFlags", ColumnType.UInt16),
+ new ColumnLayout("MemberForwarded", ColumnType.MemberForwarded,
+ GetColumnSize(ColumnType.MemberForwarded)),
+ new ColumnLayout("ImportName", ColumnType.String, StringIndexSize),
+ new ColumnLayout("ImportScope", ColumnType.ModuleRef, GetColumnSize(ColumnType.ModuleRef))),
+
+ // FieldRva
+ new TableLayout(
+ new ColumnLayout("RVA", ColumnType.UInt32),
+ new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))),
+
+ // EncLog
+ new TableLayout(
+ new ColumnLayout("Token", ColumnType.UInt32),
+ new ColumnLayout("FuncCode", ColumnType.UInt32)),
+
+ // EncMap
+ new TableLayout(
+ new ColumnLayout("Token", ColumnType.UInt32)),
+
+ // Assembly
+ new TableLayout(
+ new ColumnLayout("HashAlgId", ColumnType.UInt32),
+ new ColumnLayout("MajorVersion", ColumnType.UInt16),
+ new ColumnLayout("MinorVersion", ColumnType.UInt16),
+ new ColumnLayout("BuildNumber", ColumnType.UInt16),
+ new ColumnLayout("RevisionNumber", ColumnType.UInt16),
+ new ColumnLayout("Flags", ColumnType.UInt32),
+ new ColumnLayout("PublicKey", ColumnType.Blob, BlobIndexSize),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Culture", ColumnType.String, StringIndexSize)),
+
+ // AssemblyProcessor
+ new TableLayout(
+ new ColumnLayout("Processor", ColumnType.UInt32)),
+
+ // AssemblyOS
+ new TableLayout(
+ new ColumnLayout("PlatformId", ColumnType.UInt32),
+ new ColumnLayout("MajorVersion", ColumnType.UInt32),
+ new ColumnLayout("MinorVersion", ColumnType.UInt32)),
+
+ // AssemblyRef
+ new TableLayout(
+ new ColumnLayout("MajorVersion", ColumnType.UInt16),
+ new ColumnLayout("MinorVersion", ColumnType.UInt16),
+ new ColumnLayout("BuildNumber", ColumnType.UInt16),
+ new ColumnLayout("RevisionNumber", ColumnType.UInt16),
+ new ColumnLayout("Flags", ColumnType.UInt32),
+ new ColumnLayout("PublicKeyOrToken", ColumnType.Blob, BlobIndexSize),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Culture", ColumnType.String, StringIndexSize),
+ new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)),
+
+ // AssemblyRefProcessor
+ new TableLayout(
+ new ColumnLayout("Processor", ColumnType.UInt32),
+ new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))),
+
+ // AssemblyRefOS
+ new TableLayout(
+ new ColumnLayout("PlatformId", ColumnType.UInt32),
+ new ColumnLayout("MajorVersion", ColumnType.UInt32),
+ new ColumnLayout("MinorVersion", ColumnType.UInt32),
+ new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))),
+
+ // File
+ new TableLayout(
+ new ColumnLayout("Flags", ColumnType.UInt32),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)),
+
+ // ExportedType
+ new TableLayout(
+ new ColumnLayout("Flags", ColumnType.UInt32),
+ new ColumnLayout("TypeDefId", ColumnType.UInt32),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Namespace", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Implementation", ColumnType.Implementation,
+ GetColumnSize(ColumnType.Implementation))),
+
+ // ManifestResource
+ new TableLayout(
+ new ColumnLayout("Offset", ColumnType.UInt32),
+ new ColumnLayout("Flags", ColumnType.UInt32),
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Implementation", ColumnType.Implementation,
+ GetColumnSize(ColumnType.Implementation))),
+
+ // NestedClass
+ new TableLayout(
+ new ColumnLayout("NestedClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
+ new ColumnLayout("EnclosingClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))),
+
+ // GenericParam
+ new TableLayout(
+ new ColumnLayout("Number", ColumnType.UInt16),
+ new ColumnLayout("Flags", ColumnType.UInt16),
+ new ColumnLayout("Owner", ColumnType.TypeOrMethodDef, GetColumnSize(ColumnType.TypeOrMethodDef)),
+ new ColumnLayout("EnclosingClass", ColumnType.String, StringIndexSize)),
+
+ // MethodSpec
+ new TableLayout(
+ new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.MethodDefOrRef)),
+ new ColumnLayout("Instantiation", ColumnType.Blob, BlobIndexSize)),
+
+ // GenericParamConstraint
+ new TableLayout(
+ new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)),
+ new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))),
+
+ // Unused
+ default,
+ default,
+ default,
+
+ // Document
+ new TableLayout(
+ new ColumnLayout("Name", ColumnType.Blob, BlobIndexSize),
+ new ColumnLayout("HashAlgorithm", ColumnType.Guid, GuidIndexSize),
+ new ColumnLayout("Hash", ColumnType.Blob, BlobIndexSize),
+ new ColumnLayout("Language", ColumnType.Guid, GuidIndexSize)),
+
+ // MethodDebugInformation
+ new TableLayout(
+ new ColumnLayout("Document", ColumnType.Document, GetColumnSize(ColumnType.Document)),
+ new ColumnLayout("SequencePoints", ColumnType.Blob, BlobIndexSize)),
+
+ // LocalScope
+ new TableLayout(
+ new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)),
+ new ColumnLayout("ImportScope", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)),
+ new ColumnLayout("VariableList", ColumnType.LocalVariable, GetColumnSize(ColumnType.LocalVariable)),
+ new ColumnLayout("ConstantList", ColumnType.LocalConstant, GetColumnSize(ColumnType.LocalConstant)),
+ new ColumnLayout("StartOffset", ColumnType.UInt32),
+ new ColumnLayout("Length", ColumnType.UInt32)),
+
+ // LocalVariable
+ new TableLayout(
+ new ColumnLayout("Attributes", ColumnType.UInt16),
+ new ColumnLayout("Index", ColumnType.UInt16),
+ new ColumnLayout("VariableList", ColumnType.String, StringIndexSize)),
+
+ // LocalConstant
+ new TableLayout(
+ new ColumnLayout("Name", ColumnType.String, StringIndexSize),
+ new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
+
+ // ImportScope
+ new TableLayout(
+ new ColumnLayout("Parent", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)),
+ new ColumnLayout("Imports", ColumnType.Blob, BlobIndexSize)),
+
+ // StateMachineMethod
+ new TableLayout(
+ new ColumnLayout("MoveNextMethod", ColumnType.Method, GetColumnSize(ColumnType.Method)),
+ new ColumnLayout("KickoffMethod", ColumnType.Method, GetColumnSize(ColumnType.Method))),
+
+ // CustomDebugInformation
+ new TableLayout(
+ new ColumnLayout("Parent", ColumnType.HasCustomDebugInformation,
+ GetColumnSize(ColumnType.HasCustomDebugInformation)),
+ new ColumnLayout("Kind", ColumnType.Guid, GuidIndexSize),
+ new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)),
+ };
+ }
+}
diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs
index d8f673e46..8c6089ad1 100644
--- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs
+++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using AsmResolver.IO;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
@@ -9,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables
///
/// Represents the metadata stream containing tables defining each member in a .NET assembly.
///
- public class TablesStream : SegmentBase, IMetadataStream
+ public partial class TablesStream : SegmentBase, IMetadataStream
{
///
/// The default name of a table stream using the compressed format.
@@ -32,7 +33,7 @@ public class TablesStream : SegmentBase, IMetadataStream
public const string UncompressedStreamName = "#Schema";
private readonly Dictionary _indexEncoders;
- private readonly LazyVariable> _tables;
+ private readonly LazyVariable> _tables;
private readonly LazyVariable> _layouts;
///
@@ -41,7 +42,7 @@ public class TablesStream : SegmentBase, IMetadataStream
public TablesStream()
{
_layouts = new LazyVariable>(GetTableLayouts);
- _tables = new LazyVariable>(GetTables);
+ _tables = new LazyVariable>(GetTables);
_indexEncoders = CreateIndexEncoders();
}
@@ -192,6 +193,29 @@ public uint ExtraData
set;
}
+ ///
+ /// Gets a value indicating whether the tables stream is assigned with row counts that originate from an
+ /// external .NET metadata file.
+ ///
+ ///
+ /// This value is typically set to false , except for Portable PDB metadata table streams.
+ ///
+ [MemberNotNullWhen(true, nameof(ExternalRowCounts))]
+ public bool HasExternalRowCounts => ExternalRowCounts is not null;
+
+ ///
+ /// Gets or sets an array of row counts originating from an external .NET metadata file that this table stream
+ /// should consider when encoding indices.
+ ///
+ ///
+ /// This value is typically null , except for Portable PDB metadata table streams.
+ ///
+ public uint[]? ExternalRowCounts
+ {
+ get;
+ set;
+ }
+
///
/// Gets a collection of all tables in the tables stream.
///
@@ -199,7 +223,7 @@ public uint ExtraData
/// This collection always contains all tables, in the same order as defines, regardless
/// of whether a table actually has elements or not.
///
- protected IList Tables => _tables.Value;
+ protected IList Tables => _tables.Value;
///
/// Gets the layout of all tables in the stream.
@@ -209,6 +233,36 @@ public uint ExtraData
///
public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException();
+ ///
+ /// Obtains the implied table row count for the provided table index.
+ ///
+ /// The table index.
+ /// The row count.
+ ///
+ /// This method takes any external row counts from into account.
+ ///
+ public uint GetTableRowCount(TableIndex table)
+ {
+ return HasExternalRowCounts && (int) table < ExternalRowCounts.Length
+ ? ExternalRowCounts[(int) table]
+ : (uint) GetTable(table).Count;
+ }
+
+ ///
+ /// Obtains the implied table index size for the provided table index.
+ ///
+ /// The table index.
+ /// The index size.
+ ///
+ /// This method takes any external row counts from into account.
+ ///
+ public IndexSize GetTableIndexSize(TableIndex table)
+ {
+ return GetTableRowCount(table) > 0xFFFF
+ ? IndexSize.Long
+ : IndexSize.Short;
+ }
+
///
/// Updates the layouts of each metadata table, according to the property.
///
@@ -220,7 +274,7 @@ protected void SynchronizeTableLayoutsWithFlags()
{
var layouts = GetTableLayouts();
for (int i = 0; i < Tables.Count; i++)
- Tables[i].UpdateTableLayout(layouts[i]);
+ Tables[i]?.UpdateTableLayout(layouts[i]);
}
///
@@ -270,11 +324,11 @@ public override void Write(IBinaryStreamWriter writer)
protected virtual ulong ComputeValidBitmask()
{
// TODO: make more configurable (maybe add IMetadataTable.IsPresent property?).
-
ulong result = 0;
+
for (int i = 0; i < Tables.Count; i++)
{
- if (Tables[i].Count > 0)
+ if (Tables[i]?.Count > 0)
result |= 1UL << i;
}
@@ -288,9 +342,40 @@ protected virtual ulong ComputeValidBitmask()
/// The valid bitmask.
protected virtual ulong ComputeSortedBitmask()
{
- // TODO: make more configurable (maybe add IMetadataTable.IsSorted property?).
+ ulong result = 0;
+
+ bool containsTypeSystemData = false;
+ bool containsPdbData = false;
+
+ // Determine which tables are marked as sorted.
+ for (int i = 0; i < Tables.Count; i++)
+ {
+ if (Tables[i] is not { } table)
+ continue;
+
+ if (table.IsSorted)
+ result |= 1UL << i;
- return 0x000016003301FA00;
+ if (table.Count > 0)
+ {
+ if (i <= (int) TableIndex.MaxTypeSystemTableIndex)
+ containsTypeSystemData = true;
+ else
+ containsPdbData = true;
+ }
+ }
+
+ const ulong typeSystemMask = (1UL << (int) TableIndex.MaxTypeSystemTableIndex + 1) - 1;
+ const ulong pdbMask = ((1UL << (int) TableIndex.Max) - 1) & ~typeSystemMask;
+
+ // Backwards compatibility: Ensure that only the bits are set in the sorted mask if the metadata
+ // actually contains the type system and/or pdb tables.
+ if (!containsTypeSystemData)
+ result &= ~typeSystemMask;
+ if (!containsPdbData)
+ result &= ~pdbMask;
+
+ return result;
}
///
@@ -301,7 +386,7 @@ protected virtual ulong ComputeSortedBitmask()
protected virtual int GetTablesCount(ulong validBitmask)
{
int count = 0;
- for (TableIndex i = 0; i < (TableIndex) Tables.Count; i++)
+ for (TableIndex i = 0; i < TableIndex.Max; i++)
{
if (HasTable(validBitmask, i))
count++;
@@ -337,7 +422,7 @@ protected virtual uint GetTablesSize(ulong validBitmask)
/// The valid bitmask, indicating all present tables in the stream.
protected virtual void WriteRowCounts(IBinaryStreamWriter writer, ulong validBitmask)
{
- for (TableIndex i = 0; i < (TableIndex) Tables.Count; i++)
+ for (TableIndex i = 0; i <= TableIndex.Max; i++)
{
if (HasTable(validBitmask, i))
writer.WriteInt32(GetTable(i).Count);
@@ -380,111 +465,13 @@ protected static bool IsSorted(ulong sortedMask, TableIndex table)
return ((sortedMask >> (int) table) & 1) != 0;
}
- ///
- /// Obtains the collection of tables in the tables stream.
- ///
- /// The tables, including empty tables if there are any.
- ///
- /// This method is called upon initialization of the property.
- ///
- protected virtual IList GetTables()
- {
- var layouts = TableLayouts;
- return new IMetadataTable[]
- {
- new MetadataTable(TableIndex.Module, layouts[0]),
- new MetadataTable(TableIndex.TypeRef, layouts[1]),
- new MetadataTable(TableIndex.TypeDef, layouts[2]),
- new MetadataTable(TableIndex.FieldPtr, layouts[3]),
- new MetadataTable(TableIndex.Field, layouts[4]),
- new MetadataTable(TableIndex.Method, layouts[5]),
- new MetadataTable(TableIndex.Method, layouts[6]),
- new MetadataTable(TableIndex.ParamPtr, layouts[7]),
- new MetadataTable(TableIndex.Param, layouts[8]),
- new MetadataTable(TableIndex.InterfaceImpl, layouts[9]),
- new MetadataTable(TableIndex.MemberRef, layouts[10]),
- new MetadataTable(TableIndex.Constant, layouts[11]),
- new MetadataTable(TableIndex.CustomAttribute, layouts[12]),
- new MetadataTable(TableIndex.FieldMarshal, layouts[13]),
- new MetadataTable(TableIndex.DeclSecurity, layouts[14]),
- new MetadataTable(TableIndex.ClassLayout, layouts[15]),
- new MetadataTable(TableIndex.FieldLayout, layouts[16]),
- new MetadataTable(TableIndex.StandAloneSig, layouts[17]),
- new MetadataTable(TableIndex.EventMap, layouts[18]),
- new MetadataTable(TableIndex.EventPtr, layouts[19]),
- new MetadataTable(TableIndex.Event, layouts[20]),
- new MetadataTable(TableIndex.PropertyMap, layouts[21]),
- new MetadataTable(TableIndex.PropertyPtr, layouts[22]),
- new MetadataTable(TableIndex.Property, layouts[23]),
- new MetadataTable(TableIndex.MethodSemantics, layouts[24]),
- new MetadataTable(TableIndex.MethodImpl, layouts[25]),
- new MetadataTable(TableIndex.ModuleRef, layouts[26]),
- new MetadataTable(TableIndex.TypeSpec, layouts[27]),
- new MetadataTable(TableIndex.ImplMap, layouts[28]),
- new MetadataTable(TableIndex.FieldRva, layouts[29]),
- new MetadataTable(TableIndex.EncLog, layouts[30]),
- new MetadataTable(TableIndex.EncMap, layouts[31]),
- new MetadataTable(TableIndex.Assembly, layouts[32]),
- new MetadataTable(TableIndex.AssemblyProcessor, layouts[33]),
- new MetadataTable(TableIndex.AssemblyOS, layouts[34]),
- new MetadataTable(TableIndex.AssemblyRef, layouts[35]),
- new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[36]),
- new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[37]),
- new MetadataTable(TableIndex.File, layouts[38]),
- new MetadataTable(TableIndex.ExportedType, layouts[39]),
- new MetadataTable(TableIndex.ManifestResource, layouts[40]),
- new MetadataTable(TableIndex.NestedClass, layouts[41]),
- new MetadataTable(TableIndex.GenericParam, layouts[42]),
- new MetadataTable(TableIndex.MethodSpec, layouts[43]),
- new MetadataTable(TableIndex.GenericParamConstraint, layouts[44]),
- };
- }
-
- private Dictionary CreateIndexEncoders()
- {
- return new()
- {
- [CodedIndex.TypeDefOrRef] = new IndexEncoder(this,
- TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec),
- [CodedIndex.HasConstant] = new(this,
- TableIndex.Field, TableIndex.Param, TableIndex.Property),
- [CodedIndex.HasCustomAttribute] = new(this,
- TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef,
- TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module,
- TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig,
- TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef,
- TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam,
- TableIndex.GenericParamConstraint, TableIndex.MethodSpec),
- [CodedIndex.HasFieldMarshal] = new(this,
- TableIndex.Field, TableIndex.Param),
- [CodedIndex.HasDeclSecurity] = new(this,
- TableIndex.TypeDef, TableIndex.Method, TableIndex.Assembly),
- [CodedIndex.MemberRefParent] = new(this,
- TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef,
- TableIndex.Method, TableIndex.TypeSpec),
- [CodedIndex.HasSemantics] = new(this,
- TableIndex.Event, TableIndex.Property),
- [CodedIndex.MethodDefOrRef] = new(this,
- TableIndex.Method, TableIndex.MemberRef),
- [CodedIndex.MemberForwarded] = new(this,
- TableIndex.Field, TableIndex.Method),
- [CodedIndex.Implementation] = new(this,
- TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType),
- [CodedIndex.CustomAttributeType] = new(this,
- 0, 0, TableIndex.Method, TableIndex.MemberRef, 0),
- [CodedIndex.ResolutionScope] = new(this,
- TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef),
- [CodedIndex.TypeOrMethodDef] = new(this,
- TableIndex.TypeDef, TableIndex.Method)
- };
- }
-
///
/// Gets a table by its table index.
///
/// The table index.
/// The table.
- public virtual IMetadataTable GetTable(TableIndex index) => Tables[(int) index];
+ public virtual IMetadataTable GetTable(TableIndex index) =>
+ Tables[(int) index] ?? throw new ArgumentOutOfRangeException(nameof(index));
///
/// Gets a table by its row type.
@@ -506,7 +493,7 @@ public virtual MetadataTable GetTable()
public virtual MetadataTable GetTable(TableIndex index)
where TRow : struct, IMetadataRow
{
- return (MetadataTable) Tables[(int) index];
+ return (MetadataTable) (Tables[(int) index] ?? throw new ArgumentOutOfRangeException(nameof(index)));
}
private IndexSize GetStreamIndexSize(int bitIndex) => (IndexSize) (((((int) Flags >> bitIndex) & 1) + 1) * 2);
@@ -526,29 +513,25 @@ protected virtual uint GetColumnSize(ColumnType columnType)
{
if (_layouts.IsInitialized)
{
- if (columnType <= ColumnType.GenericParamConstraint)
- return (uint) Tables[(int) columnType].IndexSize;
- if (columnType <= ColumnType.TypeOrMethodDef)
- return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize;
+ switch (columnType)
+ {
+ case <= ColumnType.CustomDebugInformation:
+ return (uint) GetTableIndexSize((TableIndex) columnType);
+ case <= ColumnType.HasCustomDebugInformation:
+ return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize;
+ }
}
- switch (columnType)
+ return columnType switch
{
- case ColumnType.Blob:
- return (uint) BlobIndexSize;
- case ColumnType.String:
- return (uint) StringIndexSize;
- case ColumnType.Guid:
- return (uint) GuidIndexSize;
- case ColumnType.Byte:
- return sizeof(byte);
- case ColumnType.UInt16:
- return sizeof(ushort);
- case ColumnType.UInt32:
- return sizeof(uint);
- default:
- return sizeof(uint);
- }
+ ColumnType.Blob => (uint) BlobIndexSize,
+ ColumnType.String => (uint) StringIndexSize,
+ ColumnType.Guid => (uint) GuidIndexSize,
+ ColumnType.Byte => sizeof(byte),
+ ColumnType.UInt16 => sizeof(ushort),
+ ColumnType.UInt32 => sizeof(uint),
+ _ => sizeof(uint)
+ };
}
///
@@ -558,197 +541,6 @@ protected virtual uint GetColumnSize(ColumnType columnType)
/// The encoder.
public IndexEncoder GetIndexEncoder(CodedIndex index) => _indexEncoders[index];
- ///
- /// Gets an ordered collection of the current table layouts.
- ///
- /// The table layouts.
- protected TableLayout[] GetTableLayouts()
- {
- var result = new[]
- {
- new TableLayout(
- new ColumnLayout("Generation", ColumnType.UInt16),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Mvid", ColumnType.Guid, GuidIndexSize),
- new ColumnLayout("EncId", ColumnType.Guid, GuidIndexSize),
- new ColumnLayout("EncBaseId", ColumnType.Guid, GuidIndexSize)),
- new TableLayout(
- new ColumnLayout("ResolutionScope", ColumnType.ResolutionScope,
- GetColumnSize(ColumnType.ResolutionScope)),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Namespace", ColumnType.Guid, StringIndexSize)),
- new TableLayout(
- new ColumnLayout("Flags", ColumnType.UInt32),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Namespace", ColumnType.String, StringIndexSize),
- new ColumnLayout("Extends", ColumnType.TypeDefOrRef,
- GetColumnSize(ColumnType.TypeDefOrRef)),
- new ColumnLayout("FieldList", ColumnType.Field, GetColumnSize(ColumnType.Field)),
- new ColumnLayout("MethodList", ColumnType.Method, GetColumnSize(ColumnType.Method))),
- new TableLayout(
- new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))),
- new TableLayout(
- new ColumnLayout("Flags", ColumnType.UInt16),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method))),
- new TableLayout(
- new ColumnLayout("RVA", ColumnType.UInt32),
- new ColumnLayout("ImplFlags", ColumnType.UInt16),
- new ColumnLayout("Flags", ColumnType.UInt16),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize),
- new ColumnLayout("ParamList", ColumnType.Param, GetColumnSize(ColumnType.Param))),
- new TableLayout(
- new ColumnLayout("Parameter", ColumnType.Param, GetColumnSize(ColumnType.Param))),
- new TableLayout(
- new ColumnLayout("Flags", ColumnType.UInt16),
- new ColumnLayout("Sequence", ColumnType.UInt16),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize)),
- new TableLayout(
- new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
- new ColumnLayout("Interface", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))),
- new TableLayout(
- new ColumnLayout("Parent", ColumnType.MemberRefParent, GetColumnSize(ColumnType.MemberRefParent)),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Type", ColumnType.Byte),
- new ColumnLayout("Padding", ColumnType.Byte),
- new ColumnLayout("Parent", ColumnType.HasConstant, GetColumnSize(ColumnType.HasConstant)),
- new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Parent", ColumnType.HasCustomAttribute, GetColumnSize(ColumnType.HasCustomAttribute)),
- new ColumnLayout("Type", ColumnType.CustomAttributeType, GetColumnSize(ColumnType.CustomAttributeType)),
- new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Parent", ColumnType.HasFieldMarshal, GetColumnSize(ColumnType.HasFieldMarshal)),
- new ColumnLayout("NativeType", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Action", ColumnType.UInt16),
- new ColumnLayout("Parent", ColumnType.HasDeclSecurity, GetColumnSize(ColumnType.HasDeclSecurity)),
- new ColumnLayout("PermissionSet", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("PackingSize", ColumnType.UInt16),
- new ColumnLayout("ClassSize", ColumnType.UInt32),
- new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))),
- new TableLayout(
- new ColumnLayout("Offset", ColumnType.UInt32),
- new ColumnLayout("Field", ColumnType.TypeDef, GetColumnSize(ColumnType.Field))),
- new TableLayout(
- new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
- new ColumnLayout("EventList", ColumnType.Event, GetColumnSize(ColumnType.Event))),
- new TableLayout(
- new ColumnLayout("Event", ColumnType.Event, GetColumnSize(ColumnType.Event))),
- new TableLayout(
- new ColumnLayout("Flags", ColumnType.UInt16),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("EventType", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))),
- new TableLayout(
- new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
- new ColumnLayout("PropertyList", ColumnType.Event, GetColumnSize(ColumnType.Property))),
- new TableLayout(
- new ColumnLayout("Property", ColumnType.Property, GetColumnSize(ColumnType.Property))),
- new TableLayout(
- new ColumnLayout("Flags", ColumnType.UInt16),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("PropertyType", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Semantic", ColumnType.UInt16),
- new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)),
- new ColumnLayout("Association", ColumnType.HasSemantics, GetColumnSize(ColumnType.HasSemantics))),
- new TableLayout(
- new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
- new ColumnLayout("MethodBody", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef)),
- new ColumnLayout("MethodDeclaration", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef))),
- new TableLayout(
- new ColumnLayout("Name", ColumnType.String, StringIndexSize)),
- new TableLayout(
- new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("MappingFlags", ColumnType.UInt16),
- new ColumnLayout("MemberForwarded", ColumnType.MemberForwarded, GetColumnSize(ColumnType.MemberForwarded)),
- new ColumnLayout("ImportName", ColumnType.String, StringIndexSize),
- new ColumnLayout("ImportScope", ColumnType.ModuleRef, GetColumnSize(ColumnType.ModuleRef))),
- new TableLayout(
- new ColumnLayout("RVA", ColumnType.UInt32),
- new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))),
- new TableLayout(
- new ColumnLayout("Token", ColumnType.UInt32),
- new ColumnLayout("FuncCode", ColumnType.UInt32)),
- new TableLayout(
- new ColumnLayout("Token", ColumnType.UInt32)),
- new TableLayout(
- new ColumnLayout("HashAlgId", ColumnType.UInt32),
- new ColumnLayout("MajorVersion", ColumnType.UInt16),
- new ColumnLayout("MinorVersion", ColumnType.UInt16),
- new ColumnLayout("BuildNumber", ColumnType.UInt16),
- new ColumnLayout("RevisionNumber", ColumnType.UInt16),
- new ColumnLayout("Flags", ColumnType.UInt32),
- new ColumnLayout("PublicKey", ColumnType.Blob, BlobIndexSize),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Culture", ColumnType.String, StringIndexSize)),
- new TableLayout(
- new ColumnLayout("Processor", ColumnType.UInt32)),
- new TableLayout(
- new ColumnLayout("PlatformId", ColumnType.UInt32),
- new ColumnLayout("MajorVersion", ColumnType.UInt32),
- new ColumnLayout("MinorVersion", ColumnType.UInt32)),
- new TableLayout(
- new ColumnLayout("MajorVersion", ColumnType.UInt16),
- new ColumnLayout("MinorVersion", ColumnType.UInt16),
- new ColumnLayout("BuildNumber", ColumnType.UInt16),
- new ColumnLayout("RevisionNumber", ColumnType.UInt16),
- new ColumnLayout("Flags", ColumnType.UInt32),
- new ColumnLayout("PublicKeyOrToken", ColumnType.Blob, BlobIndexSize),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Culture", ColumnType.String, StringIndexSize),
- new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Processor", ColumnType.UInt32),
- new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))),
- new TableLayout(
- new ColumnLayout("PlatformId", ColumnType.UInt32),
- new ColumnLayout("MajorVersion", ColumnType.UInt32),
- new ColumnLayout("MinorVersion", ColumnType.UInt32),
- new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))),
- new TableLayout(
- new ColumnLayout("Flags", ColumnType.UInt32),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Flags", ColumnType.UInt32),
- new ColumnLayout("TypeDefId", ColumnType.UInt32),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Namespace", ColumnType.String, StringIndexSize),
- new ColumnLayout("Implementation", ColumnType.Implementation, GetColumnSize(ColumnType.Implementation))),
- new TableLayout(
- new ColumnLayout("Offset", ColumnType.UInt32),
- new ColumnLayout("Flags", ColumnType.UInt32),
- new ColumnLayout("Name", ColumnType.String, StringIndexSize),
- new ColumnLayout("Implementation", ColumnType.Implementation, GetColumnSize(ColumnType.Implementation))),
- new TableLayout(
- new ColumnLayout("NestedClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)),
- new ColumnLayout("EnclosingClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))),
- new TableLayout(
- new ColumnLayout("Number", ColumnType.UInt16),
- new ColumnLayout("Flags", ColumnType.UInt16),
- new ColumnLayout("Owner", ColumnType.TypeOrMethodDef, GetColumnSize(ColumnType.TypeOrMethodDef)),
- new ColumnLayout("EnclosingClass", ColumnType.String, StringIndexSize)),
- new TableLayout(
- new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.MethodDefOrRef)),
- new ColumnLayout("Instantiation", ColumnType.Blob, BlobIndexSize)),
- new TableLayout(
- new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)),
- new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))),
- };
-
- return result;
- }
-
///
/// Gets the range of metadata tokens referencing fields that a type defines.
///
diff --git a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs
index c75dd2a25..6ab56fa40 100644
--- a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs
+++ b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs
@@ -60,8 +60,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader
return null;
}
- return new SerializedMetadata(_context, ref directoryReader);
-
+ return DotNet.Metadata.Metadata.FromReader(directoryReader, MetadataReaderContext.FromReaderContext(_context));
}
///
@@ -126,7 +125,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader
}
var vtables = new VTableFixupsDirectory();
- vtables.UpdateOffsets(directoryReader.Offset, directoryReader.Rva);
+ vtables.UpdateOffsets(_context.GetRelocation(directoryReader.Offset, directoryReader.Rva));
for (int i = 0; i < directoryReader.Length / 8; i++)
{
diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs
index 97d19c050..4b2ebcbe4 100644
--- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs
+++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs
@@ -41,8 +41,8 @@ public VTableTokenCollection Tokens
ushort entries = reader.ReadUInt16();
var vtable = new VTableFixup((VTableType) reader.ReadUInt16());
- vtable.UpdateOffsets(offset, rva);
- vtable.Tokens.UpdateOffsets(tableReader.Offset, tableReader.Rva);
+ vtable.UpdateOffsets(context.GetRelocation(offset, rva));
+ vtable.Tokens.UpdateOffsets(context.GetRelocation(tableReader.Offset, tableReader.Rva));
for (int i = 0; i < entries; i++)
{
diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs
index d5c9e9ae2..acf7c4db5 100644
--- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs
+++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs
@@ -27,10 +27,10 @@ public uint Rva
public bool CanUpdateOffsets => true;
///
- public void UpdateOffsets(ulong newOffset, uint newRva)
+ public void UpdateOffsets(in RelocationParameters parameters)
{
- Offset = newOffset;
- Rva = newRva;
+ Offset = parameters.Offset;
+ Rva = parameters.Rva;
}
///
diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs
index 27f8998ac..4420f1fc2 100644
--- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs
+++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs
@@ -45,10 +45,10 @@ protected override void InsertItem(int index, MetadataToken item)
}
///
- public void UpdateOffsets(ulong newOffset, uint newRva)
+ public void UpdateOffsets(in RelocationParameters parameters)
{
- Offset = newOffset;
- Rva = newRva;
+ Offset = parameters.Offset;
+ Rva = parameters.Rva;
}
///
diff --git a/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs b/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs
index 4074e0bdf..d00d712bf 100644
--- a/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs
+++ b/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs
@@ -1,5 +1,6 @@
using System;
using AsmResolver.IO;
+using AsmResolver.PE.File.Headers;
namespace AsmResolver.PE.Exceptions.X64
{
@@ -151,7 +152,7 @@ public X64RuntimeFunction? ChainedFunction
public static X64UnwindInfo FromReader(PEReaderContext context, ref BinaryStreamReader reader)
{
var result = new X64UnwindInfo();
- result.UpdateOffsets(reader.Offset, reader.Rva);
+ result.UpdateOffsets(context.GetRelocation(reader.Offset, reader.Rva));
result._firstByte = reader.ReadByte();
result.SizeOfProlog = reader.ReadByte();
diff --git a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs
index 8bf5526fd..8409daf39 100644
--- a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs
+++ b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs
@@ -83,10 +83,10 @@ public void AddDirectory(IExportDirectory exportDirectory)
}
///
- public override void UpdateOffsets(ulong newOffset, uint newRva)
+ public override void UpdateOffsets(in RelocationParameters parameters)
{
- base.UpdateOffsets(newOffset, newRva);
- _contentsBuilder.UpdateOffsets(newOffset + ExportDirectoryHeaderSize, newRva + ExportDirectoryHeaderSize);
+ base.UpdateOffsets(parameters);
+ _contentsBuilder.UpdateOffsets(parameters.WithAdvance(ExportDirectoryHeaderSize));
}
///
diff --git a/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs b/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs
index f2a94395f..62275e716 100644
--- a/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs
+++ b/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs
@@ -15,23 +15,23 @@ public class HintNameTableBuffer : SegmentBase
private uint _length;
///
- public override void UpdateOffsets(ulong newOffset, uint newRva)
+ public override void UpdateOffsets(in RelocationParameters parameters)
{
- base.UpdateOffsets(newOffset, newRva);
+ base.UpdateOffsets(parameters);
- ulong currentOffset = newOffset;
+ ulong currentOffset = parameters.Offset;
foreach (var module in _modules)
{
foreach (var entry in module.Symbols)
{
if (entry.IsImportByName)
{
- _hintNameOffsets[entry] = (uint) (currentOffset - newOffset);
+ _hintNameOffsets[entry] = (uint) (currentOffset - parameters.Offset);
currentOffset += (uint) (sizeof(ushort) + Encoding.ASCII.GetByteCount(entry.Name) + 1);
currentOffset = currentOffset.Align(2);
}
}
- _moduleNameOffsets[module] = (uint) (currentOffset - newOffset);
+ _moduleNameOffsets[module] = (uint) (currentOffset - parameters.Offset);
if (module.Name is not null)
currentOffset += (uint) Encoding.ASCII.GetByteCount(module.Name);
@@ -39,7 +39,7 @@ public override void UpdateOffsets(ulong newOffset, uint newRva)
currentOffset++;
}
- _length = (uint) (currentOffset - newOffset);
+ _length = (uint) (currentOffset - parameters.Offset);
}
///
diff --git a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs
index 7bcc57074..8b24c41d0 100644
--- a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs
+++ b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs
@@ -17,17 +17,19 @@ public ImportAddressDirectoryBuffer(HintNameTableBuffer hintNameTable, bool is32
}
///
- public override void UpdateOffsets(ulong newOffset, uint newRva)
+ public override void UpdateOffsets(in RelocationParameters parameters)
{
- base.UpdateOffsets(newOffset, newRva);
+ base.UpdateOffsets(parameters);
- foreach (var module in Modules)
+ var current = parameters;
+ for (int i = 0; i < Modules.Count; i++)
{
+ var module = Modules[i];
+
var thunkTable = GetModuleThunkTable(module);
uint size = thunkTable.GetPhysicalSize();
- thunkTable.UpdateOffsets(newOffset, newRva);
- newOffset += size;
- newRva += size;
+ thunkTable.UpdateOffsets(current);
+ current.Advance(size);
}
}
diff --git a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs
index bda2b1f17..5bf7adb61 100644
--- a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs
+++ b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs
@@ -42,23 +42,22 @@ public override void AddModule(IImportedModule module)
}
///
- public override void UpdateOffsets(ulong newOffset, uint newRva)
+ public override void UpdateOffsets(in RelocationParameters parameters)
{
- base.UpdateOffsets(newOffset, newRva);
+ base.UpdateOffsets(parameters);
- newOffset += _entriesLength;
- newRva += _entriesLength;
+ var current = parameters.WithAdvance(_entriesLength);
foreach (var module in Modules)
{
var thunkTable = GetModuleThunkTable(module);
uint size = thunkTable.GetPhysicalSize();
- thunkTable.UpdateOffsets(newOffset, newRva);
- newOffset += size;
- newRva += size;
+ thunkTable.UpdateOffsets(current);
+
+ current.Advance(size);
}
- HintNameTable.UpdateOffsets(newOffset, newRva);
+ HintNameTable.UpdateOffsets(current);
}
///
diff --git a/src/AsmResolver.PE/PEReaderContext.cs b/src/AsmResolver.PE/PEReaderContext.cs
index 300247911..f5f696aaa 100644
--- a/src/AsmResolver.PE/PEReaderContext.cs
+++ b/src/AsmResolver.PE/PEReaderContext.cs
@@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using AsmResolver.PE.File;
+using AsmResolver.PE.File.Headers;
namespace AsmResolver.PE
{
@@ -29,7 +30,7 @@ public PEReaderContext(IPEFile file, PEReaderParameters parameters)
File = file;
Parameters = parameters;
}
-
+
///
/// Gets the original PE file that is being parsed.
///
@@ -46,10 +47,22 @@ public PEReaderParameters Parameters
get;
}
+ ///
+ /// Creates relocation parameters based on the current PE file that is being read.
+ ///
+ /// The offset of the segment.
+ /// The relative virtual address of the segment.
+ /// The created relocation parameters.
+ public RelocationParameters GetRelocation(ulong offset, uint rva)
+ {
+ return new RelocationParameters(File.OptionalHeader.ImageBase, offset, rva,
+ File.OptionalHeader.Magic == OptionalHeaderMagic.Pe32);
+ }
+
///
public void MarkAsFatal() => Parameters.ErrorListener.MarkAsFatal();
///
public void RegisterException(Exception exception) => Parameters.ErrorListener.RegisterException(exception);
}
-}
\ No newline at end of file
+}
diff --git a/src/AsmResolver.PE/Platforms/Amd64Platform.cs b/src/AsmResolver.PE/Platforms/Amd64Platform.cs
index 8ba103667..f0257da2f 100644
--- a/src/AsmResolver.PE/Platforms/Amd64Platform.cs
+++ b/src/AsmResolver.PE/Platforms/Amd64Platform.cs
@@ -25,9 +25,9 @@ public static Amd64Platform Instance
public override bool IsClrBootstrapperRequired => false;
///
- public override RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint)
+ public override RelocatableSegment CreateThunkStub(ISymbol entrypoint)
{
- var segment = new CodeSegment(imageBase, new byte[]
+ var segment = new CodeSegment(new byte[]
{
0x48, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rex.w rex.b mov rax, [&symbol]
0xFF, 0xE0 // jmp [rax]
diff --git a/src/AsmResolver.PE/Platforms/I386Platform.cs b/src/AsmResolver.PE/Platforms/I386Platform.cs
index 15aa92d46..e5b6b0c4b 100644
--- a/src/AsmResolver.PE/Platforms/I386Platform.cs
+++ b/src/AsmResolver.PE/Platforms/I386Platform.cs
@@ -25,11 +25,11 @@ public static I386Platform Instance
public override bool IsClrBootstrapperRequired => true;
///
- public override RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint)
+ public override RelocatableSegment CreateThunkStub(ISymbol entrypoint)
{
- var segment = new CodeSegment(imageBase, new byte[]
+ var segment = new CodeSegment(new byte[]
{
- 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 // jmp [&symbol]
+ 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 // jmp [&symbol]
});
segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute32BitAddress, entrypoint));
diff --git a/src/AsmResolver.PE/Platforms/Platform.cs b/src/AsmResolver.PE/Platforms/Platform.cs
index 506e6003d..554525644 100644
--- a/src/AsmResolver.PE/Platforms/Platform.cs
+++ b/src/AsmResolver.PE/Platforms/Platform.cs
@@ -69,10 +69,9 @@ public abstract bool IsClrBootstrapperRequired
///
/// Creates a new thunk stub that transfers control to the provided symbol.
///
- /// The image base of the image.
/// The symbol to jump to.
/// The created stub.
- public abstract RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint);
+ public abstract RelocatableSegment CreateThunkStub(ISymbol entrypoint);
///
/// Attempts to extract the original RVA from the code at the provided thunk address reader.
diff --git a/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs b/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs
index 71d31c474..92aa70a2e 100644
--- a/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs
+++ b/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs
@@ -58,9 +58,9 @@ private static RelocationBlock GetOrCreateBlock(IDictionary
- public override void UpdateOffsets(ulong newOffset, uint newRva)
+ public override void UpdateOffsets(in RelocationParameters parameters)
{
- base.UpdateOffsets(newOffset, newRva);
+ base.UpdateOffsets(parameters);
_blocks = null;
}
diff --git a/src/AsmResolver.PE/Tls/ITlsDirectory.cs b/src/AsmResolver.PE/Tls/ITlsDirectory.cs
index b07ec8000..9149c0314 100644
--- a/src/AsmResolver.PE/Tls/ITlsDirectory.cs
+++ b/src/AsmResolver.PE/Tls/ITlsDirectory.cs
@@ -54,24 +54,6 @@ TlsCharacteristics Characteristics
set;
}
- ///
- /// Gets or sets the image base address that the TLS directory assumes.
- ///
- public ulong ImageBase
- {
- get;
- set;
- }
-
- ///
- /// Gets or sets a value indicating whether the TLS directory assumes a 32-bit or 64-bit format.
- ///
- public bool Is32Bit
- {
- get;
- set;
- }
-
///
/// Obtains a collection of base address relocations that need to be applied to the TLS data directory
/// after the image was loaded into memory.
diff --git a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs
index 99c32faa1..afece2777 100644
--- a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs
+++ b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs
@@ -22,18 +22,16 @@ public SerializedTlsDirectory(PEReaderContext context, ref BinaryStreamReader re
{
_context = context;
- ulong imageBase = context.File.OptionalHeader.ImageBase;
- bool is32Bit = context.File.OptionalHeader.Magic == OptionalHeaderMagic.Pe32;
+ var relocation = context.GetRelocation(reader.Offset, reader.Rva);
- _templateStart = reader.ReadNativeInt(is32Bit);
- _templateEnd = reader.ReadNativeInt(is32Bit);
- Index = context.File.GetReferenceToRva((uint)(reader.ReadNativeInt(is32Bit) - imageBase));
- _addressOfCallbacks = reader.ReadNativeInt(is32Bit);
+ _templateStart = reader.ReadNativeInt(relocation.Is32Bit);
+ _templateEnd = reader.ReadNativeInt(relocation.Is32Bit);
+ Index = context.File.GetReferenceToRva((uint)(reader.ReadNativeInt(relocation.Is32Bit) - relocation.ImageBase));
+ _addressOfCallbacks = reader.ReadNativeInt(relocation.Is32Bit);
SizeOfZeroFill = reader.ReadUInt32();
Characteristics = (TlsCharacteristics) reader.ReadUInt32();
- ImageBase = imageBase;
- Is32Bit = is32Bit;
+ UpdateOffsets(relocation);
}
///
diff --git a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs
index ddeb3f368..dc29af2aa 100644
--- a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs
+++ b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs
@@ -9,6 +9,8 @@ namespace AsmResolver.PE.Tls
public class TlsCallbackCollection : Collection, ISegment
{
private readonly ITlsDirectory _owner;
+ private ulong _imageBase = 0x00400000;
+ private bool _is32Bit = true;
internal TlsCallbackCollection(ITlsDirectory owner)
{
@@ -33,16 +35,18 @@ public uint Rva
public bool CanUpdateOffsets => true;
///
- public void UpdateOffsets(ulong newOffset, uint newRva)
+ public void UpdateOffsets(in RelocationParameters parameters)
{
- Offset = newOffset;
- Rva = newRva;
+ Offset = parameters.Offset;
+ Rva = parameters.Rva;
+ _imageBase = parameters.ImageBase;
+ _is32Bit = parameters.Is32Bit;
}
///
public uint GetPhysicalSize()
{
- uint pointerSize = (uint) (_owner.Is32Bit ? sizeof(uint) : sizeof(ulong));
+ uint pointerSize = (uint) (_is32Bit ? sizeof(uint) : sizeof(ulong));
return (uint) (pointerSize * (Count + 1));
}
@@ -52,8 +56,8 @@ public uint GetPhysicalSize()
///
public void Write(IBinaryStreamWriter writer)
{
- ulong imageBase = _owner.ImageBase;
- bool is32Bit = _owner.Is32Bit;
+ ulong imageBase = _imageBase;
+ bool is32Bit = _is32Bit;
for (int i = 0; i < Items.Count; i++)
writer.WriteNativeInt(imageBase + Items[i].Rva, is32Bit);
diff --git a/src/AsmResolver.PE/Tls/TlsDirectory.cs b/src/AsmResolver.PE/Tls/TlsDirectory.cs
index fbe6c101c..b6bd730b1 100644
--- a/src/AsmResolver.PE/Tls/TlsDirectory.cs
+++ b/src/AsmResolver.PE/Tls/TlsDirectory.cs
@@ -12,6 +12,8 @@ public class TlsDirectory : SegmentBase, ITlsDirectory
{
private readonly LazyVariable _templateData;
private TlsCallbackCollection? _callbackFunctions;
+ private ulong _imageBase = 0x00400000;
+ private bool _is32Bit = true;
///
/// Initializes a new empty TLS data directory.
@@ -61,19 +63,12 @@ public TlsCharacteristics Characteristics
set;
}
- ///
- public ulong ImageBase
- {
- get;
- set;
- } = 0x00400000;
-
- ///
- public bool Is32Bit
+ public override void UpdateOffsets(in RelocationParameters parameters)
{
- get;
- set;
- } = true;
+ _imageBase = parameters.ImageBase;
+ _is32Bit = parameters.Is32Bit;
+ base.UpdateOffsets(in parameters);
+ }
///
/// Obtains the block of template data.
@@ -96,29 +91,30 @@ public bool Is32Bit
///
public IEnumerable GetRequiredBaseRelocations()
{
- int pointerSize = Is32Bit ? sizeof(uint) : sizeof(ulong);
- var type = Is32Bit ? RelocationType.HighLow : RelocationType.Dir64;
+ int pointerSize = _is32Bit ? sizeof(uint) : sizeof(ulong);
+ var type = _is32Bit ? RelocationType.HighLow : RelocationType.Dir64;
var result = new List(4 + CallbackFunctions.Count);
for (int i = 0; i < 4; i++)
result.Add(new BaseRelocation(type, this.ToReference(i * pointerSize)));
for (int i = 0; i < CallbackFunctions.Count; i++)
result.Add(new BaseRelocation(type, CallbackFunctions.ToReference(i * pointerSize)));
+
return result;
}
///
public override uint GetPhysicalSize()
{
- int pointerSize = Is32Bit ? sizeof(uint) : sizeof(ulong);
+ int pointerSize = _is32Bit ? sizeof(uint) : sizeof(ulong);
return (uint) (pointerSize * 4 + 2 * sizeof(uint));
}
///
public override void Write(IBinaryStreamWriter writer)
{
- ulong imageBase = ImageBase;
- bool is32Bit = Is32Bit;
+ ulong imageBase = _imageBase;
+ bool is32Bit = _is32Bit;
if (TemplateData is { } data)
{
diff --git a/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs b/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs
index 987dc8412..ea7bc9067 100644
--- a/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs
+++ b/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs
@@ -104,7 +104,7 @@ private void AddDataEntry(IResourceEntry entry)
}
///
- public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva);
+ public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters);
///
public uint GetPhysicalSize() => _segments.GetPhysicalSize();
diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj
index b0b3df765..091cf3c05 100644
--- a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj
+++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj
@@ -21,7 +21,15 @@
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs
new file mode 100644
index 000000000..32e5bccc7
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using System.Threading;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a leaf containing a list of type arguments for a function or method.
+///
+public class ArgumentListLeaf : CodeViewLeaf
+{
+ private IList? _types;
+
+ ///
+ /// Initializes an empty argument list.
+ ///
+ /// The type index to assign to the list.
+ protected ArgumentListLeaf(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Creates a new empty argument list.
+ ///
+ public ArgumentListLeaf()
+ : base(0)
+ {
+ }
+
+ ///
+ /// Creates a new argument list.
+ ///
+ public ArgumentListLeaf(params CodeViewTypeRecord[] argumentTypes)
+ : base(0)
+ {
+ _types = new List(argumentTypes);
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.ArgList;
+
+ ///
+ /// Gets an ordered collection of types that correspond to the types of each parameter.
+ ///
+ public IList Types
+ {
+ get
+ {
+ if (_types is null)
+ Interlocked.CompareExchange(ref _types, GetArgumentTypes(), null);
+ return _types;
+ }
+ }
+
+ ///
+ /// Obtains the argument types stored in the list.
+ ///
+ /// The types.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetArgumentTypes() => new List();
+
+ ///
+ public override string ToString() => string.Join(", ", Types);
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs
new file mode 100644
index 000000000..1f53f2071
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs
@@ -0,0 +1,123 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a type describing an array of elements.
+///
+public class ArrayTypeRecord : CodeViewTypeRecord
+{
+ private readonly LazyVariable _elementType;
+ private readonly LazyVariable _indexType;
+ private readonly LazyVariable _name;
+
+ ///
+ /// Initializes a new empty array type.
+ ///
+ /// The type index to assign to the type.
+ protected ArrayTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ _elementType = new LazyVariable(GetElementType);
+ _indexType = new LazyVariable(GetIndexType);
+ _name = new LazyVariable(GetName);
+ }
+
+ ///
+ /// Creates a new array type.
+ ///
+ /// The type of each element in the array.
+ /// The type to use for indexing into the array.
+ /// The number of elements in the array.
+ public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length)
+ : base(0)
+ {
+ _elementType = new LazyVariable(elementType);
+ _indexType = new LazyVariable(indexType);
+ Length = length;
+ _name = new LazyVariable(Utf8String.Empty);
+ }
+
+ ///
+ /// Creates a new array type.
+ ///
+ /// The type of each element in the array.
+ /// The type to use for indexing into the array.
+ /// The number of elements in the array.
+ /// The name of the array type.
+ public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length, Utf8String name)
+ : base(0)
+ {
+ _elementType = new LazyVariable(elementType);
+ _indexType = new LazyVariable(indexType);
+ Length = length;
+ _name = new LazyVariable(Name);
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Array;
+
+ ///
+ /// Gets or sets the type of each element in the array.
+ ///
+ public CodeViewTypeRecord? ElementType
+ {
+ get => _elementType.Value;
+ set => _elementType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the type that is used to index into the array.
+ ///
+ public CodeViewTypeRecord? IndexType
+ {
+ get => _indexType.Value;
+ set => _indexType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the number of elements in the array.
+ ///
+ public ulong Length
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the name of the type.
+ ///
+ public Utf8String Name
+ {
+ get => _name.Value;
+ set => _name.Value = value;
+ }
+
+ ///
+ /// Obtains the element type of the array.
+ ///
+ /// The element type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetElementType() => null;
+
+ ///
+ /// Obtains the index type of the array.
+ ///
+ /// The index type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetIndexType() => null;
+
+ ///
+ /// Obtains the name type of the array.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual Utf8String GetName() => Utf8String.Empty;
+
+ ///
+ public override string ToString() => $"{ElementType}[{Length}]";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs
new file mode 100644
index 000000000..3a3823945
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs
@@ -0,0 +1,62 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a reference to a base class object in a structure.
+///
+public class BaseClassField : CodeViewField
+{
+ private readonly LazyVariable _type;
+
+ ///
+ /// Initializes an empty base class.
+ ///
+ /// The type index to assign to the base class field.
+ protected BaseClassField(uint typeIndex)
+ : base(typeIndex)
+ {
+ _type = new LazyVariable(GetBaseType);
+ }
+
+ ///
+ /// Creates a new base class field.
+ ///
+ /// The base type to reference.
+ public BaseClassField(CodeViewTypeRecord type)
+ : base(0)
+ {
+ _type = new LazyVariable(type);
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BClass;
+
+ ///
+ /// Gets or sets the base type that this base class is referencing.
+ ///
+ public CodeViewTypeRecord? Type
+ {
+ get => _type.Value;
+ set => _type.Value = value;
+ }
+
+ ///
+ /// Gets or sets the offset of the base within the class.
+ ///
+ public ulong Offset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Obtains the base type that the class is referencing.
+ ///
+ /// The base type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetBaseType() => null;
+
+ ///
+ public override string ToString() => Type?.ToString() ?? "<<>>>";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs
new file mode 100644
index 000000000..40a01fbb5
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs
@@ -0,0 +1,75 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a bit field type.
+///
+public class BitFieldTypeRecord : CodeViewTypeRecord
+{
+ private readonly LazyVariable _type;
+
+ ///
+ /// Initializes an empty bit field record.
+ ///
+ /// The type index to assign to the bit field type.
+ protected BitFieldTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ _type = new LazyVariable(GetBaseType);
+ }
+
+ ///
+ /// Creates a new bit field record.
+ ///
+ /// The type of the bit field.
+ /// The bit index the bit field starts at.
+ /// The number of bits the bit field spans.
+ public BitFieldTypeRecord(CodeViewTypeRecord type, byte position, byte length)
+ : base(0)
+ {
+ _type = new LazyVariable(type);
+ Position = position;
+ Length = length;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BitField;
+
+ ///
+ /// Gets or sets the base type that this bit field is referencing.
+ ///
+ public CodeViewTypeRecord? Type
+ {
+ get => _type.Value;
+ set => _type.Value = value;
+ }
+
+ ///
+ /// Gets or sets the bit index that this bit fields starts at.
+ ///
+ public byte Position
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of bits that this bit fields spans.
+ ///
+ public byte Length
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Obtains the base type that the bit field is referencing.
+ ///
+ /// The base type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetBaseType() => null;
+
+ ///
+ public override string ToString() => $"{Type} : {Length}";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs
new file mode 100644
index 000000000..3f222effc
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs
@@ -0,0 +1,110 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a class, structure or interface type in a PDB.
+///
+public class ClassTypeRecord : CodeViewDerivedTypeRecord
+{
+ private readonly LazyVariable _uniqueName;
+ private readonly LazyVariable _vtableShape;
+
+ ///
+ /// Initializes an empty class type.
+ ///
+ /// The kind of type.
+ /// The type index to assign to the class type.
+ ///
+ /// Occurs when the provided kind is not a class, structure or interface.
+ ///
+ protected ClassTypeRecord(CodeViewLeafKind kind, uint typeIndex)
+ : base(typeIndex)
+ {
+ if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface))
+ throw new ArgumentOutOfRangeException(nameof(kind));
+
+ LeafKind = kind;
+ _uniqueName = new LazyVariable(GetUniqueName);
+ _vtableShape = new LazyVariable(GetVTableShape);
+ }
+
+ ///
+ /// Creates a new class type record.
+ ///
+ /// The kind.
+ /// The name of the type.
+ /// The unique mangled name of the type.
+ /// The size in bytes of the type.
+ /// Attributes describing the shape of the type.
+ /// The type that this type is derived from, if any.
+ ///
+ /// Occurs when the provided kind is not a class, structure or interface.
+ ///
+ public ClassTypeRecord(CodeViewLeafKind kind, Utf8String name, Utf8String uniqueName, ulong size,
+ StructureAttributes attributes, CodeViewTypeRecord? baseType)
+ : base(0)
+ {
+ if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface))
+ throw new ArgumentOutOfRangeException(nameof(kind));
+
+ LeafKind = kind;
+ Name = name;
+ _uniqueName = new LazyVariable(uniqueName);
+ _vtableShape = new LazyVariable(default(VTableShapeLeaf));
+ Size = size;
+ StructureAttributes = attributes;
+ BaseType = baseType;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind
+ {
+ get;
+ }
+
+ ///
+ /// Gets or sets the number bytes that this class spans.
+ ///
+ public ulong Size
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the uniquely identifiable name for this type.
+ ///
+ public Utf8String UniqueName
+ {
+ get => _uniqueName.Value;
+ set => _uniqueName.Value = value;
+ }
+
+ ///
+ /// Gets or sets the shape of the virtual function table of this type, if available.
+ ///
+ public VTableShapeLeaf? VTableShape
+ {
+ get => _vtableShape.Value;
+ set => _vtableShape.Value = value;
+ }
+
+ ///
+ /// Obtains the uniquely identifiable name of the type.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual Utf8String GetUniqueName() => Utf8String.Empty;
+
+ ///
+ /// Obtains the shape of the virtual function table name of the type.
+ ///
+ /// The shape.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual VTableShapeLeaf? GetVTableShape() => null;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs
new file mode 100644
index 000000000..e17d8f881
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs
@@ -0,0 +1,132 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all calling conventions that can be specified in a PDB image.
+///
+public enum CodeViewCallingConvention : byte
+{
+ ///
+ /// Indicates a near call using the cdecl calling convention.
+ ///
+ NearC = 0x00,
+
+ ///
+ /// Indicates a far call using the cdecl calling convention.
+ ///
+ FarC = 0x01,
+
+ ///
+ /// Indicates a near call using the Pascal calling convention.
+ ///
+ NearPascal = 0x02,
+
+ ///
+ /// Indicates a far call using the Pascal calling convention.
+ ///
+ FarPascal = 0x03,
+
+ ///
+ /// Indicates a near call using the fastcall calling convention.
+ ///
+ NearFast = 0x04,
+
+ ///
+ /// Indicates a far call using the fastcall calling convention.
+ ///
+ FarFast = 0x05,
+
+ ///
+ /// Skipped (unused) call index
+ ///
+ Skipped = 0x06,
+
+ ///
+ /// Indicates a near call using the stdcall calling convention.
+ ///
+ NearStd = 0x07,
+
+ ///
+ /// Indicates a far call using the stdcall calling convention.
+ ///
+ FarStd = 0x08,
+
+ ///
+ /// Indicates a near call using the syscall calling convention.
+ ///
+ NearSys = 0x09,
+
+ ///
+ /// Indicates a far call using the syscall calling convention.
+ ///
+ FarSys = 0x0a,
+
+ ///
+ /// Indicates a call using the thiscall calling convention.
+ ///
+ ThisCall = 0x0b,
+
+ ///
+ /// Indicates a call using the MIPS calling convention.
+ ///
+ MipsCall = 0x0c,
+
+ ///
+ /// Indicates a generic calling sequence.
+ ///
+ Generic = 0x0d,
+
+ ///
+ /// Indicates a call using the Alpha calling convention.
+ ///
+ AlphaCall = 0x0e,
+
+ ///
+ /// Indicates a call using the PowerPC calling convention.
+ ///
+ PpcCall = 0x0f,
+
+ ///
+ /// Indicates a call using the Hitachi SuperH calling convention.
+ ///
+ ShCall = 0x10,
+
+ ///
+ /// Indicates a call using the ARM calling convention.
+ ///
+ ArmCall = 0x11,
+
+ ///
+ /// Indicates a call using the AM33 calling convention.
+ ///
+ Am33Call = 0x12,
+
+ ///
+ /// Indicates a call using the TriCore calling convention.
+ ///
+ TriCall = 0x13,
+
+ ///
+ /// Indicates a call using the Hitachi SuperH-5 calling convention.
+ ///
+ Sh5Call = 0x14,
+
+ ///
+ /// Indicates a call using the M32R calling convention.
+ ///
+ M32RCall = 0x15,
+
+ ///
+ /// Indicates a call using the clr calling convention.
+ ///
+ ClrCall = 0x16,
+
+ ///
+ /// Marker for routines always inlined and thus lacking a convention.
+ ///
+ Inline = 0x17,
+
+ ///
+ /// Indicates a near call using the vectorcall calling convention.
+ ///
+ NearVector = 0x18,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs
new file mode 100644
index 000000000..9abc46710
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs
@@ -0,0 +1,66 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides a base for all code view types that can define one or more fields.
+///
+public abstract class CodeViewCompositeTypeRecord : CodeViewTypeRecord
+{
+ private readonly LazyVariable _name;
+ private readonly LazyVariable _fields;
+
+ ///
+ /// Initializes a new empty composite type.
+ ///
+ /// The type index to assign to the type.
+ protected CodeViewCompositeTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ _name = new LazyVariable(GetName);
+ _fields = new LazyVariable(GetFields);
+ }
+
+ ///
+ /// Gets or sets the structural attributes assigned to the type.
+ ///
+ public StructureAttributes StructureAttributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the name of the type.
+ ///
+ public Utf8String Name
+ {
+ get => _name.Value;
+ set => _name.Value = value;
+ }
+
+ ///
+ /// Gets a collection of fields that are defined in the enum.
+ ///
+ public FieldListLeaf? Fields
+ {
+ get => _fields.Value;
+ set => _fields.Value = value;
+ }
+
+ ///
+ /// Obtains the new name of the type.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual Utf8String GetName() => Utf8String.Empty;
+
+ ///
+ /// Obtains the fields defined in the type.
+ ///
+ /// The fields.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual FieldListLeaf? GetFields() => null;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs
new file mode 100644
index 000000000..3821a14fe
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs
@@ -0,0 +1,52 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a data member in a class or structure type.
+///
+public abstract class CodeViewDataField : CodeViewNamedField
+{
+ private readonly LazyVariable _dataType;
+
+ ///
+ /// Initializes an empty instance data member.
+ ///
+ /// The type index to assign to the member.
+ protected CodeViewDataField(uint typeIndex)
+ : base(typeIndex)
+ {
+ _dataType = new LazyVariable(GetDataType);
+ }
+
+ ///
+ /// Creates a new data member.
+ ///
+ /// The data type of the member.
+ /// The name of the member.
+ protected CodeViewDataField(CodeViewTypeRecord dataType, Utf8String name)
+ : base(0)
+ {
+ _dataType = new LazyVariable(dataType);
+ Name = name;
+ }
+
+ ///
+ /// Gets or sets the data type of the member.
+ ///
+ public CodeViewTypeRecord DataType
+ {
+ get => _dataType.Value;
+ set => _dataType.Value = value;
+ }
+
+ ///
+ /// Obtains the data type of the member.
+ ///
+ /// The data type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetDataType() => null;
+
+ ///
+ public override string ToString() => $"{DataType} {Name}";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs
new file mode 100644
index 000000000..3cd51d0f8
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs
@@ -0,0 +1,37 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides a base for all custom types that may be derived from a base type.
+///
+public abstract class CodeViewDerivedTypeRecord : CodeViewCompositeTypeRecord
+{
+ private readonly LazyVariable _baseType;
+
+ ///
+ protected CodeViewDerivedTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ _baseType = new LazyVariable(GetBaseType);
+ }
+
+ ///
+ /// Gets or sets the base type that this type is deriving from.
+ ///
+ public CodeViewTypeRecord? BaseType
+ {
+ get => _baseType.Value;
+ set => _baseType.Value = value;
+ }
+
+ ///
+ /// Obtains the type that the type is derived from.
+ ///
+ /// The type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetBaseType() => null;
+
+ ///
+ public override string ToString() => Name;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs
new file mode 100644
index 000000000..689105e37
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs
@@ -0,0 +1,32 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a single record in a field list of a TPI or IPI stream.
+///
+public abstract class CodeViewField : CodeViewLeaf
+{
+ ///
+ /// Initializes an empty CodeView field leaf.
+ ///
+ /// The type index to assign to the leaf.
+ protected CodeViewField(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Gets or sets the attributes associated to the field.
+ ///
+ public CodeViewFieldAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets a value indicating whether the field is a newly introduced virtual function.
+ ///
+ public bool IsIntroducingVirtual =>
+ (Attributes & CodeViewFieldAttributes.IntroducingVirtual) == CodeViewFieldAttributes.IntroducingVirtual
+ || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) == CodeViewFieldAttributes.PureIntroducingVirtual;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs
new file mode 100644
index 000000000..efbea90ec
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs
@@ -0,0 +1,95 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all flags that can be assigned to a field, method or class.
+///
+[Flags]
+public enum CodeViewFieldAttributes : ushort
+{
+ ///
+ /// Indicates no attributes were assigned to the field.
+ ///
+ None = 0b00000000_00000000,
+
+ ///
+ /// Indicates the field is marked private.
+ ///
+ Private = 0b00000000_00000001,
+
+ ///
+ /// Indicates the field is marked protected.
+ ///
+ Protected = 0b00000000_00000010,
+
+ ///
+ /// Indicates the field is marked public.
+ ///
+ Public = 0b00000000_00000011,
+
+ ///
+ /// Provides the bit-mask that can be used to extract the access-level of the field.
+ ///
+ AccessMask = 0b00000000_00000011,
+
+ ///
+ /// Indicates the method is a virtual method.
+ ///
+ Virtual = 0b00000000_00000100,
+
+ ///
+ /// Indicates the method is a static method.
+ ///
+ Static = 0b00000000_00001000,
+
+ ///
+ /// Indicates the method can be accessed only from within the current module.
+ ///
+ Friend = 0b00000000_00001100,
+
+ ///
+ /// Indicates the method is a new introducing virtual method.
+ ///
+ IntroducingVirtual = 0b00000000_00010000,
+
+ ///
+ /// Indicates the method is a pure virtual method.
+ ///
+ PureVirtual = 0b00000000_00010100,
+
+ ///
+ /// Indicates the method is a new introducing pure virtual method.
+ ///
+ PureIntroducingVirtual = 0b00000000_00011000,
+
+ ///
+ /// Provides the bit-mask that can be used to extract the method properties of the field.
+ ///
+ MethodPropertiesMask = 0b00000000_00011100,
+
+ ///
+ /// Indicates the field is compiler generated and does not exist.
+ ///
+ Pseudo = 0b00000000_00100000,
+
+ ///
+ /// Indicates the class cannot be inherited.
+ ///
+ NoInherit = 0b00000000_01000000,
+
+ ///
+ /// Indicates the class cannot be constructed.
+ ///
+ NoConstruct = 0b00000000_10000000,
+
+ ///
+ /// Indicates the field is compiler generated but does exist.
+ ///
+ CompilerGenerated = 0b00000001_00000000,
+
+ ///
+ /// Indicates the method cannot be overridden.
+ ///
+ Sealed = 0b00000010_00000000
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs
new file mode 100644
index 000000000..c966aa91e
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs
@@ -0,0 +1,99 @@
+using AsmResolver.IO;
+using AsmResolver.Symbols.Pdb.Leaves.Serialized;
+using static AsmResolver.Symbols.Pdb.Leaves.CodeViewLeafKind;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a single leaf record in a TPI or IPI stream.
+///
+public abstract class CodeViewLeaf
+{
+ ///
+ /// Initializes an empty CodeView leaf.
+ ///
+ /// The type index to assign to the leaf.
+ protected CodeViewLeaf(uint typeIndex)
+ {
+ TypeIndex = typeIndex;
+ }
+
+ ///
+ /// Gets the type kind this record encodes.
+ ///
+ public abstract CodeViewLeafKind LeafKind
+ {
+ get;
+ }
+
+ ///
+ /// Gets the type index the type is associated to.
+ ///
+ public uint TypeIndex
+ {
+ get;
+ internal set;
+ }
+
+ internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ {
+ ushort length = reader.ReadUInt16();
+ var dataReader = reader.Fork();
+ reader.Offset += length;
+
+ return FromReaderNoHeader(context, typeIndex, ref dataReader);
+ }
+
+ internal static CodeViewLeaf FromReaderNoHeader(
+ PdbReaderContext context,
+ uint typeIndex,
+ ref BinaryStreamReader dataReader)
+ {
+ var kind = (CodeViewLeafKind) dataReader.ReadUInt16();
+ return kind switch
+ {
+ Array => new SerializedArrayTypeRecord(context, typeIndex, dataReader),
+ ArgList => new SerializedArgumentListLeaf(context, typeIndex, dataReader),
+ BClass => new SerializedBaseClassField(context, typeIndex, ref dataReader),
+ Class or Interface or Structure => new SerializedClassTypeRecord(kind, context, typeIndex, dataReader),
+ BitField => new SerializedBitFieldTypeRecord(context, typeIndex, dataReader),
+ Enum => new SerializedEnumTypeRecord(context, typeIndex, dataReader),
+ Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader),
+ FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader),
+ Member => new SerializedInstanceDataField(context, typeIndex, ref dataReader),
+ Method => new SerializedOverloadedMethod(context, typeIndex, ref dataReader),
+ MethodList => new SerializedMethodListLeaf(context, typeIndex, dataReader),
+ MFunction => new SerializedMemberFunctionLeaf(context, typeIndex, dataReader),
+ Modifier => new SerializedModifierTypeRecord(context, typeIndex, dataReader),
+ NestType or NestTypeEx => new SerializedNestedTypeField(context, typeIndex, ref dataReader),
+ OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader),
+ Pointer => new SerializedPointerTypeRecord(context, typeIndex, dataReader),
+ Procedure => new SerializedProcedureTypeRecord(context, typeIndex, dataReader),
+ StMember => new SerializedStaticDataField(context, typeIndex, ref dataReader),
+ Union => new SerializedUnionTypeRecord(context, typeIndex, dataReader),
+ VFuncTab => new SerializedVTableField(context, typeIndex, ref dataReader),
+ VTShape => new SerializedVTableShapeLeaf(context, typeIndex, dataReader),
+ VBClass or IVBClass => new SerializedVBaseClassField(context, typeIndex, ref dataReader, kind == IVBClass),
+ _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd())
+ };
+ }
+
+ internal static object ReadNumeric(ref BinaryStreamReader reader)
+ {
+ var kind = (CodeViewLeafKind) reader.ReadUInt16();
+ return kind switch
+ {
+ < Numeric => (object) (uint) kind,
+ Char => (char) reader.ReadByte(),
+ Short => reader.ReadInt16(),
+ UShort => reader.ReadUInt16(),
+ Long => reader.ReadInt32(),
+ ULong => reader.ReadUInt32(),
+ QuadWord => reader.ReadInt64(),
+ UQuadWord => reader.ReadUInt64(),
+ Real32 => reader.ReadSingle(),
+ Real64 => reader.ReadDouble(),
+ _ => 0
+ };
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs
new file mode 100644
index 000000000..6acb79d56
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs
@@ -0,0 +1,210 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all possible type record kinds that can be stored in a TPI or IPI stream.
+///
+public enum CodeViewLeafKind : ushort
+{
+ ///
+ /// This is not really a type defined in PDB spec, but is used to indicate simple types.
+ ///
+ SimpleType = 0xffff,
+
+#pragma warning disable CS1591
+ Modifier16T = 0x0001,
+ Pointer16T = 0x0002,
+ Array16T = 0x0003,
+ Class16T = 0x0004,
+ Structure16T = 0x0005,
+ Union16T = 0x0006,
+ Enum16T = 0x0007,
+ Procedure16T = 0x0008,
+ MFunction16T = 0x0009,
+ VTShape = 0x000a,
+ Cobol016T = 0x000b,
+ Cobol1 = 0x000c,
+ Barray16T = 0x000d,
+ Label = 0x000e,
+ Null = 0x000f,
+ Nottran = 0x0010,
+ DimArray16T = 0x0011,
+ VftPath16T = 0x0012,
+ PreComp16T = 0x0013,
+ EndPreComp = 0x0014,
+ Oem16T = 0x0015,
+ TypeServerSt = 0x0016,
+
+ Skip16T = 0x0200,
+ ArgList16T = 0x0201,
+ DefArg16T = 0x0202,
+ List = 0x0203,
+ FieldList16T = 0x0204,
+ Derived16T = 0x0205,
+ BitField16T = 0x0206,
+ MethodList16T = 0x0207,
+ DimConU16T = 0x0208,
+ DimConLu16T = 0x0209,
+ DimVarU16T = 0x020a,
+ DimVarLu16T = 0x020b,
+ RefSym = 0x020c,
+
+ BClass16T = 0x0400,
+ VBClass16T = 0x0401,
+ IVBClass16T = 0x0402,
+ EnumerateSt = 0x0403,
+ FriendFcn16T = 0x0404,
+ Index16T = 0x0405,
+ Member16T = 0x0406,
+ StMember16T = 0x0407,
+ Method16T = 0x0408,
+ NestType16T = 0x0409,
+ VFuncTab16T = 0x040a,
+ FriendCls16T = 0x040b,
+ OneMethod16T = 0x040c,
+ VFuncOff16T = 0x040d,
+
+ Ti16Max = 0x1000,
+
+ Modifier = 0x1001,
+ Pointer = 0x1002,
+ ArraySt = 0x1003,
+ ClassSt = 0x1004,
+ StructureSt = 0x1005,
+ UnionSt = 0x1006,
+ EnumSt = 0x1007,
+ Procedure = 0x1008,
+ MFunction = 0x1009,
+ Cobol0 = 0x100a,
+ BArray = 0x100b,
+ DimArraySt = 0x100c,
+ VftPath = 0x100d,
+ PreCompSt = 0x100e,
+ Oem = 0x100f,
+ AliasSt = 0x1010,
+ Oem2 = 0x1011,
+
+ Skip = 0x1200,
+ ArgList = 0x1201,
+ DefArgSt = 0x1202,
+ FieldList = 0x1203,
+ Derived = 0x1204,
+ BitField = 0x1205,
+ MethodList = 0x1206,
+ DimConU = 0x1207,
+ DimConLu = 0x1208,
+ DimVarU = 0x1209,
+ DimVarLu = 0x120a,
+
+ BClass = 0x1400,
+ VBClass = 0x1401,
+ IVBClass = 0x1402,
+ FriendFcnSt = 0x1403,
+ Index = 0x1404,
+ MemberSt = 0x1405,
+ StmemberSt = 0x1406,
+ MethodSt = 0x1407,
+ NestTypeSt = 0x1408,
+ VFuncTab = 0x1409,
+ FriendCls = 0x140a,
+ OneMethodSt = 0x140b,
+ VFuncOff = 0x140c,
+ NestTypeExSt = 0x140d,
+ MemberModifySt = 0x140e,
+ ManagedSt = 0x140f,
+
+ StMax = 0x1500,
+
+ TypeServer = 0x1501,
+ Enumerate = 0x1502,
+ Array = 0x1503,
+ Class = 0x1504,
+ Structure = 0x1505,
+ Union = 0x1506,
+ Enum = 0x1507,
+ DimArray = 0x1508,
+ PreComp = 0x1509,
+ Alias = 0x150a,
+ DefArg = 0x150b,
+ FriendFcn = 0x150c,
+ Member = 0x150d,
+ StMember = 0x150e,
+ Method = 0x150f,
+ NestType = 0x1510,
+ OneMethod = 0x1511,
+ NestTypeEx = 0x1512,
+ MemberModify = 0x1513,
+ Managed = 0x1514,
+ TypeServer2 = 0x1515,
+
+ StridedArray = 0x1516,
+ Hlsl = 0x1517,
+ ModifierEx = 0x1518,
+ Interface = 0x1519,
+ BInterface = 0x151a,
+ Vector = 0x151b,
+ Matrix = 0x151c,
+
+ VFTable = 0x151d,
+ EndOfLeafRecord = VFTable,
+
+ TypeLast,
+ TypeMax = TypeLast - 1,
+
+ FuncId = 0x1601,
+ MFuncId = 0x1602,
+ Buildinfo = 0x1603,
+ SubstrList = 0x1604,
+ StringId = 0x1605,
+
+ UdtSrcLine = 0x1606,
+ UdtModSrcLine = 0x1607,
+
+ IdLast,
+ IdMax = IdLast - 1,
+
+ Numeric = 0x8000,
+ Char = 0x8000,
+ Short = 0x8001,
+ UShort = 0x8002,
+ Long = 0x8003,
+ ULong = 0x8004,
+ Real32 = 0x8005,
+ Real64 = 0x8006,
+ Real80 = 0x8007,
+ Real128 = 0x8008,
+ QuadWord = 0x8009,
+ UQuadWord = 0x800a,
+ Real48 = 0x800b,
+ Complex32 = 0x800c,
+ Complex64 = 0x800d,
+ Complex80 = 0x800e,
+ Complex128 = 0x800f,
+ VarString = 0x8010,
+
+ OctWord = 0x8017,
+ UOctWord = 0x8018,
+
+ Decimal = 0x8019,
+ Date = 0x801a,
+ Utf8String = 0x801b,
+
+ Real16 = 0x801c,
+
+ Pad0 = 0xf0,
+ Pad1 = 0xf1,
+ Pad2 = 0xf2,
+ Pad3 = 0xf3,
+ Pad4 = 0xf4,
+ Pad5 = 0xf5,
+ Pad6 = 0xf6,
+ Pad7 = 0xf7,
+ Pad8 = 0xf8,
+ Pad9 = 0xf9,
+ Pad10 = 0xfa,
+ Pad11 = 0xfb,
+ Pad12 = 0xfc,
+ Pad13 = 0xfd,
+ Pad14 = 0xfe,
+ Pad15 = 0xff,
+#pragma warning restore CS1591
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs
new file mode 100644
index 000000000..3137dabde
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs
@@ -0,0 +1,40 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a single record in a field list that is assigned a name.
+///
+public abstract class CodeViewNamedField : CodeViewField
+{
+ private readonly LazyVariable _name;
+
+ ///
+ /// Initializes an empty CodeView field leaf.
+ ///
+ /// The type index to assign to the leaf.
+ protected CodeViewNamedField(uint typeIndex)
+ : base(typeIndex)
+ {
+ _name = new LazyVariable(GetName);
+ }
+
+ ///
+ /// Gets or sets the name of the field.
+ ///
+ public Utf8String Name
+ {
+ get => _name.Value;
+ set => _name.Value = value;
+ }
+
+ ///
+ /// Obtains the name of the field.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual Utf8String GetName() => Utf8String.Empty;
+
+ ///
+ public override string ToString() => Name;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs
new file mode 100644
index 000000000..097f66be0
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs
@@ -0,0 +1,16 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a single type record in a TPI or IPI stream.
+///
+public abstract class CodeViewTypeRecord : CodeViewLeaf
+{
+ ///
+ /// Initializes an empty CodeView type record.
+ ///
+ /// The type index to assign to the leaf.
+ protected CodeViewTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs
new file mode 100644
index 000000000..a88bc9c73
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs
@@ -0,0 +1,33 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents an enum type.
+///
+public class EnumTypeRecord : CodeViewDerivedTypeRecord
+{
+ ///
+ /// Initializes a new empty enum type.
+ ///
+ /// The type index to assign to the enum type.
+ protected EnumTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Creates a new enum type.
+ ///
+ /// The name of the enum.
+ /// The underlying type of all members in the enum.
+ /// The structural attributes assigned to the enum.
+ public EnumTypeRecord(Utf8String name, CodeViewTypeRecord underlyingType, StructureAttributes attributes)
+ : base(0)
+ {
+ Name = name;
+ BaseType = underlyingType;
+ StructureAttributes = attributes;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enum;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs
new file mode 100644
index 000000000..4fe1a97c9
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs
@@ -0,0 +1,57 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a single enumerate field leaf in a field list.
+///
+public class EnumerateField : CodeViewNamedField
+{
+ private readonly LazyVariable _value;
+
+ ///
+ /// Initializes an empty enumerate field leaf.
+ ///
+ /// The type index to assign to the enumerate field.
+ protected EnumerateField(uint typeIndex)
+ : base(typeIndex)
+ {
+ _value = new LazyVariable(GetValue);
+ }
+
+ ///
+ /// Creates a new enumerate field leaf.
+ ///
+ /// The name of the field.
+ /// The value assigned to the field.
+ /// The attributes associated to the field.
+ public EnumerateField(Utf8String name, object value, CodeViewFieldAttributes attributes)
+ : base(0)
+ {
+ Name = name;
+ _value = new LazyVariable(value);
+ Attributes = attributes;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enumerate;
+
+ ///
+ /// Gets or sets the constant value assigned to the field.
+ ///
+ public object Value
+ {
+ get => _value.Value;
+ set => _value.Value = value;
+ }
+
+ ///
+ /// Obtains the value assigned to the field.
+ ///
+ /// The value.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual object? GetValue() => null;
+
+ ///
+ public override string ToString() => $"{Name} = {Value}";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs
new file mode 100644
index 000000000..5eecc0abe
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Threading;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a leaf containing a list of fields.
+///
+public class FieldListLeaf : CodeViewLeaf
+{
+ private IList? _fields;
+
+ ///
+ /// Initializes an empty field list.
+ ///
+ /// The type index to assign to the list.
+ protected FieldListLeaf(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Creates a new empty field list.
+ ///
+ public FieldListLeaf()
+ : base(0)
+ {
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList;
+
+ ///
+ /// Gets a collection of fields stored in the list.
+ ///
+ public IList Entries
+ {
+ get
+ {
+ if (_fields is null)
+ Interlocked.CompareExchange(ref _fields, GetEntries(), null);
+ return _fields;
+ }
+ }
+
+ ///
+ /// Obtains the fields stored in the list.
+ ///
+ /// The fields
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetEntries() => new List();
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs
new file mode 100644
index 000000000..cc3bf9448
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs
@@ -0,0 +1,43 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents an instance data member in a class or structure type.
+///
+public class InstanceDataField : CodeViewDataField
+{
+ ///
+ /// Initializes an empty instance data member.
+ ///
+ /// The type index to assign to the member.
+ protected InstanceDataField(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Creates a new instance data member.
+ ///
+ /// The name of the member.
+ /// The byte offset within the class or structure that the member is stored at.
+ /// The data type of the member.
+ public InstanceDataField(Utf8String name, ulong offset, CodeViewTypeRecord dataType)
+ : base(dataType, name)
+ {
+ Offset = offset;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Member;
+
+ ///
+ /// Gets or sets the byte offset within the class or structure that the member is stored at.
+ ///
+ public ulong Offset
+ {
+ get;
+ set;
+ }
+
+ ///
+ public override string ToString() => $"+{Offset:X4}: {DataType} {Name}";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs
new file mode 100644
index 000000000..2fbcbb55c
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all attributes that can be assigned to a member function.
+///
+[Flags]
+public enum MemberFunctionAttributes : byte
+{
+ ///
+ /// Indicates if the function is a C++ style ReturnUDT function.
+ ///
+ CxxReturnUdt = 1,
+
+ ///
+ /// Indicates the function is an instance constructor.
+ ///
+ Ctor = 2,
+
+ ///
+ /// Indicates the function is an instance constructor of a class with virtual bases.
+ ///
+ CtorVBase = 4
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs
new file mode 100644
index 000000000..5069796fb
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs
@@ -0,0 +1,154 @@
+using System.Linq;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a single instance member function.
+///
+public class MemberFunctionLeaf : CodeViewLeaf
+{
+ private readonly LazyVariable _returnType;
+ private readonly LazyVariable _declaringType;
+ private readonly LazyVariable _thisType;
+ private readonly LazyVariable _argumentList;
+
+ ///
+ /// Initializes an empty member function.
+ ///
+ /// The type index to assign to the function.
+ protected MemberFunctionLeaf(uint typeIndex)
+ : base(typeIndex)
+ {
+ _returnType = new LazyVariable(GetReturnType);
+ _declaringType = new LazyVariable(GetDeclaringType);
+ _thisType = new LazyVariable(GetThisType);
+ _argumentList = new LazyVariable(GetArguments);
+ }
+
+ ///
+ /// Creates a new member function.
+ ///
+ /// The return type of the function.
+ /// The declaring type of the function.
+ /// The argument types of the function.
+ public MemberFunctionLeaf(CodeViewTypeRecord returnType, CodeViewTypeRecord declaringType, ArgumentListLeaf arguments)
+ : base(0)
+ {
+ _returnType = new LazyVariable(returnType);
+ _declaringType = new LazyVariable(declaringType);
+ _thisType = new LazyVariable(default(CodeViewTypeRecord));
+ _argumentList = new LazyVariable(arguments);
+ CallingConvention = CodeViewCallingConvention.NearC;
+ Attributes = 0;
+ ThisAdjuster = 0;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MFunction;
+
+ ///
+ /// Gets or sets the return type of the function.
+ ///
+ public CodeViewTypeRecord? ReturnType
+ {
+ get => _returnType.Value;
+ set => _returnType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the type that declares this member function.
+ ///
+ public CodeViewTypeRecord? DeclaringType
+ {
+ get => _declaringType.Value;
+ set => _declaringType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the type of the this pointer that is used to access the member function.
+ ///
+ public CodeViewTypeRecord? ThisType
+ {
+ get => _thisType.Value;
+ set => _thisType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the convention that is used when calling the member function.
+ ///
+ public CodeViewCallingConvention CallingConvention
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the attributes associated to the function.
+ ///
+ public MemberFunctionAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the list of types of the parameters that this function defines.
+ ///
+ public ArgumentListLeaf? Arguments
+ {
+ get => _argumentList.Value;
+ set => _argumentList.Value = value;
+ }
+
+ ///
+ /// Gets or sets the offset to adjust the this pointer with before devirtualization of this method.
+ ///
+ public uint ThisAdjuster
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Obtains the return type of the function.
+ ///
+ /// The return type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetReturnType() => null;
+
+ ///
+ /// Obtains the declaring type of the function.
+ ///
+ /// The declaring type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetDeclaringType() => null;
+
+ ///
+ /// Obtains the this-type of the function.
+ ///
+ /// The this-type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetThisType() => null;
+
+ ///
+ /// Obtains the argument types of the function.
+ ///
+ /// The argument types.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual ArgumentListLeaf? GetArguments() => null;
+
+ ///
+ public override string ToString()
+ {
+ string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty());
+ return $"{CallingConvention} {ReturnType} {DeclaringType}::*({args})";
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs
new file mode 100644
index 000000000..447296798
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs
@@ -0,0 +1,94 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents one single entry in a list of overloaded methods.
+///
+public class MethodListEntry
+{
+ private readonly LazyVariable _function;
+
+ ///
+ /// Initializes an empty method list entry.
+ ///
+ protected MethodListEntry()
+ {
+ _function = new LazyVariable(GetFunction);
+ }
+
+ ///
+ /// Creates a new method list entry.
+ ///
+ /// The attributes associated to this method.
+ /// The referenced function.
+ public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function)
+ {
+ Attributes = attributes;
+ _function = new LazyVariable(function);
+ VTableOffset = 0;
+ }
+
+ ///
+ /// Creates a new method list entry.
+ ///
+ /// The attributes associated to this method.
+ /// The referenced function.
+ /// The offset to the slot the virtual function table that this method occupies.
+ public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function, uint vTableOffset)
+ {
+ Attributes = attributes;
+ _function = new LazyVariable(function);
+ VTableOffset = vTableOffset;
+ }
+
+ ///
+ /// Gets or sets the attributes associated to this method.
+ ///
+ public CodeViewFieldAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the function that is referenced by this method.
+ ///
+ public MemberFunctionLeaf? Function
+ {
+ get => _function.Value;
+ set => _function.Value = value;
+ }
+
+ ///
+ /// Gets a value indicating whether the function is a newly introduced virtual function.
+ ///
+ public bool IsIntroducingVirtual =>
+ (Attributes & CodeViewFieldAttributes.IntroducingVirtual) != 0
+ || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) != 0;
+
+ ///
+ /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function
+ /// table that this method occupies.
+ ///
+ public uint VTableOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Obtains the function that this method references.
+ ///
+ /// The function.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual MemberFunctionLeaf? GetFunction() => null;
+
+ ///
+ public override string ToString()
+ {
+ return IsIntroducingVirtual
+ ? $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}, {nameof(VTableOffset)}: {VTableOffset}"
+ : $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}";
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs
new file mode 100644
index 000000000..f9538bac3
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs
@@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a leaf record containing a list of overloaded methods.
+///
+public class MethodListLeaf : CodeViewLeaf
+{
+ private IList? _entries;
+
+ ///
+ /// Initializes an empty method list.
+ ///
+ /// The type index to assign to the list.
+ protected MethodListLeaf(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Creates a new empty method list.
+ ///
+ public MethodListLeaf()
+ : base(0)
+ {
+ }
+
+ ///
+ /// Creates a new method list.
+ ///
+ /// The methods to include.
+ public MethodListLeaf(params MethodListEntry[] entries)
+ : base(0)
+ {
+ _entries = entries.ToList();
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MethodList;
+
+ ///
+ /// Gets a collection of methods stored in the list.
+ ///
+ public IList Entries
+ {
+ get
+ {
+ if (_entries is null)
+ Interlocked.CompareExchange(ref _entries, GetEntries(), null);
+ return _entries;
+ }
+ }
+
+ ///
+ /// Obtains the methods stored in the list.
+ ///
+ /// The methods
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetEntries() => new List();
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs
new file mode 100644
index 000000000..3c221e900
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all possible modifiers that can be added to a type using a Modifier type record in a
+/// TPI or IPI stream.
+///
+[Flags]
+public enum ModifierAttributes : ushort
+{
+ ///
+ /// Indicates the type is marked as const.
+ ///
+ Const = 1,
+
+ ///
+ /// Indicates the type is marked as volatile.
+ ///
+ Volatile = 2,
+
+ ///
+ /// Indicates the type is marked as unaligned.
+ ///
+ Unaligned = 4,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs
new file mode 100644
index 000000000..c70e50ed1
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs
@@ -0,0 +1,94 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a type that is annotated with extra modifiers.
+///
+public class ModifierTypeRecord : CodeViewTypeRecord
+{
+ private readonly LazyVariable _baseType;
+
+ ///
+ /// Initializes a new empty modifier type.
+ ///
+ /// The type index to assign to the modifier type.
+ protected ModifierTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ _baseType = new LazyVariable(GetBaseType);
+ }
+
+ ///
+ /// Creates a new modified type.
+ ///
+ /// The type to be modified.
+ /// The attributes describing the shape of the pointer.
+ public ModifierTypeRecord(CodeViewTypeRecord type, ModifierAttributes attributes)
+ : base(0)
+ {
+ _baseType = new LazyVariable(type);
+ Attributes = attributes;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Modifier;
+
+ ///
+ /// Gets or sets the type that is annotated.
+ ///
+ public CodeViewTypeRecord BaseType
+ {
+ get => _baseType.Value;
+ set => _baseType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the annotations that were added to the type.
+ ///
+ public ModifierAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the type is marked as const.
+ ///
+ public bool IsConst
+ {
+ get => (Attributes & ModifierAttributes.Const) != 0;
+ set => Attributes = (Attributes & ~ModifierAttributes.Const)
+ | (value ? ModifierAttributes.Const : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the type is marked as volatile.
+ ///
+ public bool IsVolatile
+ {
+ get => (Attributes & ModifierAttributes.Volatile) != 0;
+ set => Attributes = (Attributes & ~ModifierAttributes.Volatile)
+ | (value ? ModifierAttributes.Volatile : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the type is marked as unaligned.
+ ///
+ public bool IsUnaligned
+ {
+ get => (Attributes & ModifierAttributes.Unaligned) != 0;
+ set => Attributes = (Attributes & ~ModifierAttributes.Unaligned)
+ | (value ? ModifierAttributes.Unaligned : 0);
+ }
+
+ ///
+ /// Obtains the base type of the modifier type.
+ ///
+ /// The base type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetBaseType() => null;
+
+ ///
+ public override string ToString() => $"{BaseType} {Attributes}";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs
new file mode 100644
index 000000000..91bca6ee3
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs
@@ -0,0 +1,69 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a field in a type that references a nested type definition.
+///
+public class NestedTypeField : CodeViewNamedField
+{
+ private readonly LazyVariable _type;
+
+ ///
+ /// Initializes an empty nested type.
+ ///
+ /// The type index to assign to the nested type field.
+ protected NestedTypeField(uint typeIndex)
+ : base(typeIndex)
+ {
+ _type = new LazyVariable(GetNestedType);
+ }
+
+ ///
+ /// Creates a new nested type field.
+ ///
+ /// The definition of the nested type
+ /// The name of the nested type.
+ public NestedTypeField(CodeViewTypeRecord type, Utf8String name)
+ : base(0)
+ {
+ _type = new LazyVariable(type);
+ Name = name;
+ Attributes = 0;
+ }
+
+ ///
+ /// Creates a new nested type (extended) field.
+ ///
+ /// The definition of the nested type
+ /// The name of the nested type.
+ /// The attributes assigned to the type.
+ public NestedTypeField(CodeViewTypeRecord type, Utf8String name, CodeViewFieldAttributes attributes)
+ : base(0)
+ {
+ _type = new LazyVariable(type);
+ Name = name;
+ Attributes = attributes;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => Attributes == 0
+ ? CodeViewLeafKind.NestType
+ : CodeViewLeafKind.NestTypeEx;
+
+ ///
+ /// Gets or sets the definition of the referenced nested type.
+ ///
+ public CodeViewTypeRecord? Type
+ {
+ get => _type.Value;
+ set => _type.Value = value;
+ }
+
+ ///
+ /// Obtains the definition of the nested type.
+ ///
+ /// The type
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetNestedType() => null;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs
new file mode 100644
index 000000000..08bdb113f
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs
@@ -0,0 +1,80 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a single method in a type.
+///
+public class NonOverloadedMethod : CodeViewNamedField
+{
+ private readonly LazyVariable _function;
+
+ ///
+ /// Initializes an empty non-overloaded method.
+ ///
+ /// The type index to assign to the method.
+ protected NonOverloadedMethod(uint typeIndex)
+ : base(typeIndex)
+ {
+ _function = new LazyVariable(GetFunction);
+ }
+
+ ///
+ /// Creates a new overloaded method.
+ ///
+ /// The name of the method.
+ /// The attributes associated to the method.
+ /// The function that is referenced by the method.
+ public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, MemberFunctionLeaf function)
+ : base(0)
+ {
+ _function = new LazyVariable(function);
+ Attributes = attributes;
+ Name = name;
+ }
+
+ ///
+ /// Creates a new overloaded method.
+ ///
+ /// The name of the method.
+ /// The attributes associated to the method.
+ /// The offset to the slot the virtual function table that this method occupies.
+ /// The function that is referenced by the method.
+ public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, uint vTableOffset, MemberFunctionLeaf function)
+ : base(0)
+ {
+ _function = new LazyVariable(function);
+ Attributes = attributes;
+ Name = name;
+ VTableOffset = vTableOffset;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.OneMethod;
+
+ ///
+ /// Gets or sets the function that is referenced by this method.
+ ///
+ public MemberFunctionLeaf? Function
+ {
+ get => _function.Value;
+ set => _function.Value = value;
+ }
+
+ ///
+ /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function
+ /// table that this method occupies.
+ ///
+ public uint VTableOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Obtains the function that this method references.
+ ///
+ /// The function.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual MemberFunctionLeaf? GetFunction() => null;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs
new file mode 100644
index 000000000..a6c17cea0
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs
@@ -0,0 +1,67 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a method that is overloaded by one or more functions.
+///
+public class OverloadedMethod : CodeViewNamedField
+{
+ private readonly LazyVariable _methods;
+
+ ///
+ /// Initializes an empty overloaded method.
+ ///
+ /// The type index to assign to the method.
+ protected OverloadedMethod(uint typeIndex)
+ : base(typeIndex)
+ {
+ _methods = new LazyVariable(GetMethods);
+ }
+
+ ///
+ /// Creates a new empty overloaded method.
+ ///
+ public OverloadedMethod()
+ : base(0)
+ {
+ _methods = new LazyVariable(new MethodListLeaf());
+ }
+
+ ///
+ /// Creates a new overloaded method.
+ ///
+ public OverloadedMethod(MethodListLeaf methods)
+ : base(0)
+ {
+ _methods = new LazyVariable(methods);
+ }
+
+ ///
+ /// Creates a new overloaded method.
+ ///
+ public OverloadedMethod(params MethodListEntry[] methods)
+ : base(0)
+ {
+ _methods = new LazyVariable(new MethodListLeaf(methods));
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Method;
+
+ ///
+ /// Gets or sets a list of methods that were overloaded.
+ ///
+ public MethodListLeaf? Methods
+ {
+ get => _methods.Value;
+ set => _methods.Value = value;
+ }
+
+ ///
+ /// Obtains the list of methods that were overloaded.
+ ///
+ /// The methods.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual MethodListLeaf? GetMethods() => null;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs
new file mode 100644
index 000000000..a1c9ac743
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs
@@ -0,0 +1,145 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all possible flags that can be assigned to a pointer type in a TPI or IPI stream.
+///
+[Flags]
+public enum PointerAttributes : uint
+{
+ ///
+ /// Indicates the pointer is a 16 bit pointer.
+ ///
+ Near16 = 0b0000000000_000_000000_00000_000_00000,
+
+ ///
+ /// Indicates the pointer is a 16:16 far pointer.
+ ///
+ Far16 = 0b0000000000_000_000000_00000_000_000001,
+
+ ///
+ /// Indicates the pointer is a 16:16 huge pointer.
+ ///
+ Huge16 = 0b0000000000_000_000000_00000_000_000010,
+
+ ///
+ /// Indicates the pointer is a based on segment.
+ ///
+ BasedOnSegment = 0b0000000000_000_000000_00000_000_000011,
+
+ ///
+ /// Indicates the pointer is a based on value of base.
+ ///
+ BasedOnValue = 0b0000000000_000_000000_00000_000_000100,
+
+ ///
+ /// Indicates the pointer is a based on segment value of base.
+ ///
+ BasedOnSegmentValue = 0b0000000000_000_000000_00000_000_000101,
+
+ ///
+ /// Indicates the pointer is a based on address of base.
+ ///
+ BasedOnAddress = 0b0000000000_000_000000_00000_000_000110,
+
+ ///
+ /// Indicates the pointer is a based on segment address of base.
+ ///
+ BasedOnSegmentAddress = 0b0000000000_000_000000_00000_000_000111,
+
+ ///
+ /// Indicates the pointer is a based on type.
+ ///
+ BasedOnType = 0b0000000000_000_000000_00000_000_001000,
+
+ ///
+ /// Indicates the pointer is a based on self.
+ ///
+ BasedOnSelf = 0b0000000000_000_000000_00000_000_001001,
+
+ ///
+ /// Indicates the pointer is a 32 bit pointer.
+ ///
+ Near32 = 0b0000000000_000_000000_00000_000_001010,
+
+ ///
+ /// Indicates the pointer is a 16:32 pointer.
+ ///
+ Far32 = 0b0000000000_000_000000_00000_000_001011,
+
+ ///
+ /// Indicates the pointer is a 64 bit pointer.
+ ///
+ Near64 = 0b0000000000_000_000000_00000_000_001100,
+
+ ///
+ /// Provides the bit-mask for extracting the pointer kind from the flags.
+ ///
+ KindMask = 0b0000000000_000_000000_00000_000_11111,
+
+ ///
+ /// Indicates the pointer is an "old" reference.
+ ///
+ LValueReference = 0b0000000000_000_000000_00000_001_00000,
+
+ ///
+ /// Indicates the pointer is a pointer to data member.
+ ///
+ PointerToDataMember = 0b0000000000_000_000000_00000_010_00000,
+
+ ///
+ /// Indicates the pointer is a pointer to member function.
+ ///
+ PointerToMemberFunction = 0b0000000000_000_000000_00000_011_00000,
+
+ ///
+ /// Indicates the pointer is an r-value reference.
+ ///
+ RValueReference = 0b0000000000_000_000000_00000_100_00000,
+
+ ///
+ /// Provides the bit-mask for extracting the pointer mode from the flags.
+ ///
+ ModeMask = 0b0000000000_000_000000_00000_111_00000,
+
+ ///
+ /// Indicates the pointer is a "flat" pointer.
+ ///
+ Flat32 = 0b0000000000_000_000000_00001_000_00000,
+
+ ///
+ /// Indicates the pointer is marked volatile.
+ ///
+ Volatile = 0b0000000000_000_000000_00010_000_00000,
+
+ ///
+ /// Indicates the pointer is marked const.
+ ///
+ Const = 0b0000000000_000_000000_00100_000_00000,
+
+ ///
+ /// Indicates the pointer is marked unaligned.
+ ///
+ Unaligned = 0b0000000000_000_000000_01000_000_00000,
+
+ ///
+ /// Indicates the pointer is marked restrict.
+ ///
+ Restrict = 0b0000000000_000_000000_10000_000_00000,
+
+ ///
+ /// Indicates the pointer is a WinRT smart pointer.
+ ///
+ WinRTSmartPointer = 0b0000000000_001_000000_00000_000_00000,
+
+ ///
+ /// Indicates the pointer is a 'this' pointer of a member function with ref qualifier.
+ ///
+ LValueRefThisPointer = 0b0000000000_010_000000_00000_000_00000,
+
+ ///
+ /// Indicates the pointer is a 'this' pointer of a member function with ref qualifier.
+ ///
+ RValueRefThisPointer = 0b0000000000_100_000000_00000_000_00000
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs
new file mode 100644
index 000000000..740e792cd
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs
@@ -0,0 +1,356 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a pointer type in a TPI or IPI stream.
+///
+public class PointerTypeRecord : CodeViewTypeRecord
+{
+ private readonly LazyVariable _baseType;
+
+ ///
+ /// Initializes a new empty pointer type.
+ ///
+ /// The type index to assign to the type.
+ protected PointerTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ _baseType = new LazyVariable(GetBaseType);
+ }
+
+ ///
+ /// Creates a new pointer type.
+ ///
+ /// The referent type.
+ /// The attributes describing the shape of the pointer.
+ public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes)
+ : base(0)
+ {
+ _baseType = new LazyVariable(type);
+ Attributes = attributes;
+ }
+
+ ///
+ /// Creates a new pointer type.
+ ///
+ /// The referent type.
+ /// The attributes describing the shape of the pointer.
+ /// The size of the pointer.
+ public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes, byte size)
+ : base(0)
+ {
+ _baseType = new LazyVariable(type);
+ Attributes = attributes;
+ Size = size;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Pointer;
+
+ ///
+ /// Gets or sets the referent type of the pointer.
+ ///
+ public CodeViewTypeRecord BaseType
+ {
+ get => _baseType.Value;
+ set => _baseType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the attributes describing the shape of the pointer type.
+ ///
+ public PointerAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the kind of the pointer.
+ ///
+ public PointerAttributes Kind
+ {
+ get => Attributes & PointerAttributes.KindMask;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask) | (value & PointerAttributes.KindMask);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a 16 bit pointer.
+ ///
+ public bool IsNear16
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near16;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.Near16 : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a 16:16 far pointer.
+ ///
+ public bool IsFar16
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Far16;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.Far16 : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a 16:16 huge pointer.
+ ///
+ public bool IsHuge16
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Huge16;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.Huge16 : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a based on segment.
+ ///
+ public bool IsBasedOnSegment
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegment;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.BasedOnSegment : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a based on value of base.
+ ///
+ public bool IsBasedOnValue
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnValue;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.BasedOnValue : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a based on segment value of base.
+ ///
+ public bool IsBasedOnSegmentValue
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegmentValue;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.BasedOnSegmentValue : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a based on address of base.
+ ///
+ public bool IsBasedOnAddress
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnAddress;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.BasedOnAddress : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a based on segment address of base.
+ ///
+ public bool IsBasedOnSegmentAddress
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegmentAddress;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.BasedOnSegmentAddress : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a based on type.
+ ///
+ public bool IsBasedOnType
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnType;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.BasedOnType : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a based on self.
+ ///
+ public bool IsBasedOnSelf
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSelf;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.BasedOnSelf : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a 32 bit pointer.
+ ///
+ public bool IsNear32
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near32;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.Near32 : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a 16:32 pointer.
+ ///
+ public bool IsFar32
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Far32;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.Far32 : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a 64 bit pointer.
+ ///
+ public bool IsNear64
+ {
+ get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near64;
+ set => Attributes = (Attributes & ~PointerAttributes.KindMask)
+ | (value ? PointerAttributes.Near64 : 0);
+ }
+
+ ///
+ /// Gets or sets the mode of the pointer.
+ ///
+ public PointerAttributes Mode
+ {
+ get => Attributes & PointerAttributes.ModeMask;
+ set => Attributes = (Attributes & ~PointerAttributes.ModeMask) | (value & PointerAttributes.ModeMask);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is an "old" reference.
+ ///
+ public bool IsLValueReference
+ {
+ get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.LValueReference;
+ set => Attributes = (Attributes & ~PointerAttributes.ModeMask)
+ | (value ? PointerAttributes.LValueReference : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a pointer to data member.
+ ///
+ public bool IsPointerToDataMember
+ {
+ get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.PointerToDataMember;
+ set => Attributes = (Attributes & ~PointerAttributes.ModeMask)
+ | (value ? PointerAttributes.PointerToDataMember : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a pointer to member function.
+ ///
+ public bool IsPointerToMemberFunction
+ {
+ get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.PointerToMemberFunction;
+ set => Attributes = (Attributes & ~PointerAttributes.ModeMask)
+ | (value ? PointerAttributes.PointerToMemberFunction : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is an r-value reference.
+ ///
+ public bool IsRValueReference
+ {
+ get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.RValueReference;
+ set => Attributes = (Attributes & ~PointerAttributes.ModeMask)
+ | (value ? PointerAttributes.RValueReference : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a "flat" pointer.
+ ///
+ public bool IsFlat32
+ {
+ get => (Attributes & PointerAttributes.Flat32) != 0;
+ set => Attributes = (Attributes & ~PointerAttributes.Flat32)
+ | (value ? PointerAttributes.Flat32 : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is marked volatile.
+ ///
+ public bool IsVolatile
+ {
+ get => (Attributes & PointerAttributes.Volatile) != 0;
+ set => Attributes = (Attributes & ~PointerAttributes.Volatile)
+ | (value ? PointerAttributes.Volatile : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is marked const.
+ ///
+ public bool IsConst
+ {
+ get => (Attributes & PointerAttributes.Const) != 0;
+ set => Attributes = (Attributes & ~PointerAttributes.Const)
+ | (value ? PointerAttributes.Const : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is marked unaligned.
+ ///
+ public bool IsUnaligned
+ {
+ get => (Attributes & PointerAttributes.Unaligned) != 0;
+ set => Attributes = (Attributes & ~PointerAttributes.Unaligned)
+ | (value ? PointerAttributes.Unaligned : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is marked restrict.
+ ///
+ public bool IsRestrict
+ {
+ get => (Attributes & PointerAttributes.Restrict) != 0;
+ set => Attributes = (Attributes & ~PointerAttributes.Restrict)
+ | (value ? PointerAttributes.Restrict : 0);
+ }
+
+ ///
+ /// Gets or sets the size of the pointer.
+ ///
+ public byte Size
+ {
+ get => (byte) (((uint) Attributes >> 0xD) & 0b111111);
+ set => Attributes = (PointerAttributes) (((uint) Attributes & ~(0b111111u << 0xD))
+ | (((uint) value & 0b111111) << 0xD));
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a WinRT smart pointer.
+ ///
+ public bool IsWinRTSmartPointer
+ {
+ get => (Attributes & PointerAttributes.WinRTSmartPointer) != 0;
+ set => Attributes = (Attributes & ~PointerAttributes.WinRTSmartPointer)
+ | (value ? PointerAttributes.WinRTSmartPointer : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a 'this' pointer of a member function with ref qualifier.
+ ///
+ public bool IsLValueRefThisPointer
+ {
+ get => (Attributes & PointerAttributes.LValueRefThisPointer) != 0;
+ set => Attributes = (Attributes & ~PointerAttributes.LValueRefThisPointer)
+ | (value ? PointerAttributes.LValueRefThisPointer : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the pointer is a 'this' pointer of a member function with ref qualifier.
+ ///
+ public bool IsRValueRefThisPointer
+ {
+ get => (Attributes & PointerAttributes.RValueRefThisPointer) != 0;
+ set => Attributes = (Attributes & ~PointerAttributes.RValueRefThisPointer)
+ | (value ? PointerAttributes.RValueRefThisPointer : 0);
+ }
+
+ ///
+ /// Obtains the base type of the pointer.
+ ///
+ /// The base type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetBaseType() => null;
+
+ ///
+ public override string ToString() => $"({BaseType})*";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs
new file mode 100644
index 000000000..b973fcccb
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs
@@ -0,0 +1,101 @@
+using System.Linq;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a function pointer or procedure type.
+///
+public class ProcedureTypeRecord : CodeViewTypeRecord
+{
+ private readonly LazyVariable _returnType;
+ private readonly LazyVariable _argumentList;
+
+ ///
+ /// Initializes an empty procedure type.
+ ///
+ /// The type index to assign to the type.
+ protected ProcedureTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ _returnType = new LazyVariable(GetReturnType);
+ _argumentList = new LazyVariable(GetArguments);
+ }
+
+ ///
+ /// Creates a new procedure type.
+ ///
+ /// The convention to use when calling the function pointed by values of this type.
+ /// The return type of the function.
+ /// The argument type list of the function.
+ public ProcedureTypeRecord(CodeViewCallingConvention callingConvention, CodeViewTypeRecord returnType, ArgumentListLeaf arguments)
+ : base(0)
+ {
+ CallingConvention = callingConvention;
+ _returnType = new LazyVariable(returnType);
+ _argumentList = new LazyVariable(arguments);
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Procedure;
+
+ ///
+ /// Gets or sets the return type of the function.
+ ///
+ public CodeViewTypeRecord? ReturnType
+ {
+ get => _returnType.Value;
+ set => _returnType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the convention that is used when calling the member function.
+ ///
+ public CodeViewCallingConvention CallingConvention
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the attributes associated to the function.
+ ///
+ public MemberFunctionAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the list of types of the parameters that this function defines.
+ ///
+ public ArgumentListLeaf? Arguments
+ {
+ get => _argumentList.Value;
+ set => _argumentList.Value = value;
+ }
+
+ ///
+ /// Obtains the return type of the procedure.
+ ///
+ /// The return type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetReturnType() => null;
+
+ ///
+ /// Obtains the argument types of the procedure..
+ ///
+ /// The argument types.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual ArgumentListLeaf? GetArguments() => null;
+
+ ///
+ public override string ToString()
+ {
+ string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty());
+ return $"{CallingConvention} {ReturnType} *({args})";
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs
new file mode 100644
index 000000000..a8a54409c
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedArgumentListLeaf : ArgumentListLeaf
+{
+ private readonly PdbReaderContext _context;
+ private readonly BinaryStreamReader _reader;
+
+ ///
+ /// Reads a argument list from the provided input stream.
+ ///
+ /// The reading context in which the list is situated in.
+ /// The type index to assign to the list.
+ /// The input stream to read from.
+ public SerializedArgumentListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _reader = reader;
+ }
+
+ ///
+ protected override IList GetArgumentTypes()
+ {
+ var reader = _reader.Fork();
+
+ int count = reader.ReadInt32();
+ var result = new List(count);
+
+ for (int i = 0; i < count; i++)
+ {
+ uint typeIndex = reader.ReadUInt32();
+ if (!_context.ParentImage.TryGetLeafRecord(typeIndex, out var leaf) || leaf is not CodeViewTypeRecord t)
+ {
+ _context.Parameters.ErrorListener.BadImage(
+ $"Argument list {TypeIndex:X8} contains an invalid argument type index {typeIndex:X8}.");
+ return result;
+ }
+
+ result.Add(t);
+ }
+
+ return result;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs
new file mode 100644
index 000000000..273df7536
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs
@@ -0,0 +1,52 @@
+using System;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedArrayTypeRecord : ArrayTypeRecord
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _elementTypeIndex;
+ private readonly uint _indexTypeIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads an array type from the provided input stream.
+ ///
+ /// The reading context in which the type is situated in.
+ /// The type index to assign to the type.
+ /// The input stream to read from.
+ public SerializedArrayTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _elementTypeIndex = reader.ReadUInt32();
+ _indexTypeIndex = reader.ReadUInt32();
+ Length = Convert.ToUInt64(ReadNumeric(ref reader));
+ _nameReader = reader.Fork();
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override CodeViewTypeRecord? GetElementType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_elementTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Array type {TypeIndex:X8} contains an invalid element type index {_elementTypeIndex:X8}.");
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetIndexType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_indexTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Array type {TypeIndex:X8} contains an invalid index type index {_indexTypeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs
new file mode 100644
index 000000000..325e73010
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs
@@ -0,0 +1,37 @@
+using System;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedBaseClassField : BaseClassField
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _baseTypeIndex;
+
+ ///
+ /// Reads a base class from the provided input stream.
+ ///
+ /// The reading context in which the class is situated in.
+ /// The type index to assign to the type.
+ /// The input stream to read from.
+ public SerializedBaseClassField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ Attributes = (CodeViewFieldAttributes) reader.ReadUInt16();
+ _baseTypeIndex = reader.ReadUInt32();
+ Offset = Convert.ToUInt64(ReadNumeric(ref reader));
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetBaseType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Base class {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs
new file mode 100644
index 000000000..e18e87dfe
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs
@@ -0,0 +1,36 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedBitFieldTypeRecord : BitFieldTypeRecord
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _baseTypeIndex;
+
+ ///
+ /// Reads a bit field type from the provided input stream.
+ ///
+ /// The reading context in which the bit field type is situated in.
+ /// The type index to assign to the type.
+ /// The input stream to read from.
+ public SerializedBitFieldTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _baseTypeIndex = reader.ReadUInt32();
+ Length = reader.ReadByte();
+ Position = reader.ReadByte();
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetBaseType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Bit field type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs
new file mode 100644
index 000000000..3773352ff
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs
@@ -0,0 +1,83 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedClassTypeRecord : ClassTypeRecord
+{
+ private readonly PdbReaderContext _context;
+ private readonly ushort _memberCount;
+ private readonly uint _baseTypeIndex;
+ private readonly uint _fieldIndex;
+ private readonly uint _vTableShapeIndex;
+ private readonly BinaryStreamReader _nameReader;
+ private readonly BinaryStreamReader _uniqueNameReader;
+
+ ///
+ /// Reads a class type from the provided input stream.
+ ///
+ /// The kind of type that is being read.
+ /// The reading context in which the type is situated in.
+ /// The type index to assign to the type.
+ /// The input stream to read from.
+ public SerializedClassTypeRecord(CodeViewLeafKind kind, PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(kind, typeIndex)
+ {
+ _context = context;
+ _memberCount = reader.ReadUInt16();
+ StructureAttributes = (StructureAttributes) reader.ReadUInt16();
+ _fieldIndex = reader.ReadUInt32();
+ _baseTypeIndex = reader.ReadUInt32();
+ _vTableShapeIndex = reader.ReadUInt32();
+
+ Size = (uint) ReadNumeric(ref reader);
+
+ _nameReader = reader.Fork();
+ reader.AdvanceUntil(0, true);
+ _uniqueNameReader = reader.Fork();
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override CodeViewTypeRecord? GetBaseType()
+ {
+ if (_baseTypeIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Class type {TypeIndex:X8} contains an invalid underlying enum type index {_baseTypeIndex:X8}.");
+ }
+
+ ///
+ protected override FieldListLeaf? GetFields()
+ {
+ if (_fieldIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list
+ ? list
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}.");
+ }
+
+ ///
+ protected override VTableShapeLeaf? GetVTableShape()
+ {
+ if (_vTableShapeIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_vTableShapeIndex, out var leaf) && leaf is VTableShapeLeaf shape
+ ? shape
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Class type {TypeIndex:X8} contains an invalid VTable shape index {_fieldIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs
new file mode 100644
index 000000000..e671bb176
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs
@@ -0,0 +1,56 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedEnumTypeRecord : EnumTypeRecord
+{
+ private readonly PdbReaderContext _context;
+ private readonly ushort _memberCount;
+ private readonly uint _underlyingType;
+ private readonly uint _fieldIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads a constant symbol from the provided input stream.
+ ///
+ /// The reading context in which the symbol is situated in.
+ /// The type index to assign to the enum type.
+ /// The input stream to read from.
+ public SerializedEnumTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _memberCount = reader.ReadUInt16();
+ StructureAttributes = (StructureAttributes) reader.ReadUInt16();
+ _underlyingType = reader.ReadUInt32();
+ _fieldIndex = reader.ReadUInt32();
+ _nameReader = reader;
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override CodeViewTypeRecord? GetBaseType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}.");
+ }
+
+ ///
+ protected override FieldListLeaf? GetFields()
+ {
+ if (_fieldIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is FieldListLeaf list
+ ? list
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Enum type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs
new file mode 100644
index 000000000..91457f27f
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs
@@ -0,0 +1,32 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedEnumerateField : EnumerateField
+{
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads a enumerate field list from the provided input stream.
+ ///
+ /// The reading context in which the enumerate field is situated in.
+ /// The type index to assign to the enum type.
+ /// The input stream to read from.
+ public SerializedEnumerateField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ Attributes = (CodeViewFieldAttributes) reader.ReadUInt16();
+
+ // We need to eagerly initialize the value because it is the only way to know how large the leaf is.
+ Value = ReadNumeric(ref reader);
+
+ _nameReader = reader;
+ reader.AdvanceUntil(0, true);
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs
new file mode 100644
index 000000000..2eddbc7d3
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+internal class SerializedFieldListLeaf : FieldListLeaf
+{
+ private readonly PdbReaderContext _context;
+ private readonly BinaryStreamReader _reader;
+
+ ///
+ /// Reads a field list from the provided input stream.
+ ///
+ /// The reading context in which the list is situated in.
+ /// The type index to assign to the enum type.
+ /// The input stream to read from.
+ public SerializedFieldListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _reader = reader;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList;
+
+ protected override IList GetEntries()
+ {
+ var reader = _reader.Fork();
+ var result = new List();
+
+ while (reader.CanRead(sizeof(ushort)))
+ {
+ // Skip padding bytes.
+ while (reader.CanRead(sizeof(byte)))
+ {
+ var b = (CodeViewLeafKind) reader.ReadByte();
+ if (b < CodeViewLeafKind.Pad0)
+ {
+ reader.Offset--;
+ break;
+ }
+ }
+
+ if (!reader.CanRead(sizeof(byte)))
+ break;
+
+ // Read field.
+ var leaf = FromReaderNoHeader(_context, 0, ref reader);
+ if (leaf is CodeViewField field)
+ result.Add(field);
+ else
+ _context.Parameters.ErrorListener.BadImage($"Field list contains a non-field leaf {leaf.LeafKind}.");
+ }
+
+ return result;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs
new file mode 100644
index 000000000..c5fcf252e
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs
@@ -0,0 +1,49 @@
+using System;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedInstanceDataField : InstanceDataField
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _dataTypeIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads an instance data member list from the provided input stream.
+ ///
+ /// The reading context in which the member is situated in.
+ /// The type index to assign to the member.
+ /// The input stream to read from.
+ public SerializedInstanceDataField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ Attributes = (CodeViewFieldAttributes) reader.ReadUInt16();
+ _dataTypeIndex = reader.ReadUInt32();
+
+ // We need to eagerly initialize the offset because it is the only way to know how large the leaf is.
+ Offset = Convert.ToUInt64(ReadNumeric(ref reader));
+
+ _context = context;
+ _nameReader = reader;
+ reader.AdvanceUntil(0, true);
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override CodeViewTypeRecord? GetDataType()
+ {
+ if (_dataTypeIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Instance data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs
new file mode 100644
index 000000000..1b0cc2253
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs
@@ -0,0 +1,75 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedMemberFunctionLeaf : MemberFunctionLeaf
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _returnTypeIndex;
+ private readonly uint _declaringTypeIndex;
+ private readonly uint _thisTypeIndex;
+ private readonly ushort _parameterCount;
+ private readonly uint _argumentListIndex;
+
+ ///
+ /// Reads a member function from the provided input stream.
+ ///
+ /// The reading context in which the function is situated in.
+ /// The index to assign to the type.
+ /// The input stream to read from.
+ public SerializedMemberFunctionLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _returnTypeIndex = reader.ReadUInt32();
+ _declaringTypeIndex = reader.ReadUInt32();
+ _thisTypeIndex = reader.ReadUInt32();
+ CallingConvention = (CodeViewCallingConvention) reader.ReadByte();
+ Attributes = (MemberFunctionAttributes) reader.ReadByte();
+ _parameterCount = reader.ReadUInt16();
+ _argumentListIndex = reader.ReadUInt32();
+ ThisAdjuster = reader.ReadUInt32();
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetReturnType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Member function {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}.");
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetDeclaringType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_declaringTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Member function {TypeIndex:X8} contains an invalid declaring type index {_declaringTypeIndex:X8}.");
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetThisType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_thisTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Member function {TypeIndex:X8} contains an invalid this-type index {_thisTypeIndex:X8}.");
+ }
+
+ ///
+ protected override ArgumentListLeaf? GetArguments()
+ {
+ if (_argumentListIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list
+ ? list
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Member function {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs
new file mode 100644
index 000000000..af17da782
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs
@@ -0,0 +1,35 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedMethodListEntry : MethodListEntry
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _functionIndex;
+
+ ///
+ /// Reads a member function from the provided input stream.
+ ///
+ /// The reading context in which the type is situated in.
+ /// The input stream to read from.
+ public SerializedMethodListEntry(PdbReaderContext context, ref BinaryStreamReader reader)
+ {
+ _context = context;
+ Attributes = (CodeViewFieldAttributes) reader.ReadUInt16();
+ reader.ReadUInt16(); // padding
+ _functionIndex = reader.ReadUInt32();
+ VTableOffset = IsIntroducingVirtual ? reader.ReadUInt32() : 0;
+ }
+
+ ///
+ protected override MemberFunctionLeaf? GetFunction()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Method list entry contains an invalid function type index {_functionIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs
new file mode 100644
index 000000000..36927a164
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedMethodListLeaf : MethodListLeaf
+{
+ private readonly PdbReaderContext _context;
+ private readonly BinaryStreamReader _reader;
+
+ ///
+ /// Reads a method list from the provided input stream.
+ ///
+ /// The reading context in which the list is situated in.
+ /// The type index to assign to the enum type.
+ /// The input stream to read from.
+ public SerializedMethodListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _reader = reader;
+ }
+
+ ///
+ protected override IList GetEntries()
+ {
+ var result = new List();
+
+ var reader = _reader.Fork();
+ while (reader.CanRead(8))
+ result.Add(new SerializedMethodListEntry(_context, ref reader));
+
+ return result;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs
new file mode 100644
index 000000000..a6c9b5bf3
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs
@@ -0,0 +1,35 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedModifierTypeRecord : ModifierTypeRecord
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _baseTypeIndex;
+
+ ///
+ /// Reads a pointer type from the provided input stream.
+ ///
+ /// The reading context in which the type is situated in.
+ /// The index to assign to the type.
+ /// The input stream to read from.
+ public SerializedModifierTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _baseTypeIndex = reader.ReadUInt32();
+ Attributes = (ModifierAttributes) reader.ReadUInt32();
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetBaseType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Modifier type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs
new file mode 100644
index 000000000..9ebefafc0
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs
@@ -0,0 +1,41 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedNestedTypeField : NestedTypeField
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _typeIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads a nested type field from the provided input stream.
+ ///
+ /// The reading context in which the field is situated in.
+ /// The index to assign to the field.
+ /// The input stream to read from.
+ public SerializedNestedTypeField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ Attributes = (CodeViewFieldAttributes) reader.ReadUInt16();
+ _typeIndex = reader.ReadUInt32();
+ _nameReader = reader.Fork();
+ reader.AdvanceUntil(0, true);
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override CodeViewTypeRecord? GetNestedType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Nested type {TypeIndex:X8} contains an invalid type index {_typeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs
new file mode 100644
index 000000000..08347ed9d
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs
@@ -0,0 +1,43 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedNonOverloadedMethod : NonOverloadedMethod
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _functionIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads a non-overloaded method from the provided input stream.
+ ///
+ /// The reading context in which the method is situated in.
+ /// The index to assign to the type.
+ /// The input stream to read from.
+ public SerializedNonOverloadedMethod(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ Attributes = (CodeViewFieldAttributes) reader.ReadUInt16();
+ _functionIndex = reader.ReadUInt32();
+ if (IsIntroducingVirtual)
+ VTableOffset = reader.ReadUInt32();
+ _nameReader = reader.Fork();
+ reader.AdvanceUntil(0, true);
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override MemberFunctionLeaf? GetFunction()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Method {TypeIndex:X8} contains an invalid function type index {_functionIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs
new file mode 100644
index 000000000..3342850e3
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs
@@ -0,0 +1,45 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedOverloadedMethod : OverloadedMethod
+{
+ private readonly PdbReaderContext _context;
+ private readonly ushort _functionCount;
+ private readonly uint _methodListIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads an overloaded method from the provided input stream.
+ ///
+ /// The reading context in which the method is situated in.
+ /// The index to assign to the type.
+ /// The input stream to read from.
+ public SerializedOverloadedMethod(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _functionCount = reader.ReadUInt16();
+ _methodListIndex = reader.ReadUInt32();
+ _nameReader = reader.Fork();
+ reader.AdvanceUntil(0, true);
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override MethodListLeaf? GetMethods()
+ {
+ if (_methodListIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_methodListIndex, out var leaf) && leaf is MethodListLeaf list
+ ? list
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Overloaded method {TypeIndex:X8} contains an invalid field list index {_methodListIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs
new file mode 100644
index 000000000..74a481134
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs
@@ -0,0 +1,38 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedPointerTypeRecord : PointerTypeRecord
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _baseTypeIndex;
+
+ ///
+ /// Reads a pointer type from the provided input stream.
+ ///
+ /// The reading context in which the type is situated in.
+ /// The index to assign to the type.
+ /// The input stream to read from.
+ public SerializedPointerTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _baseTypeIndex = reader.ReadUInt32();
+ Attributes = (PointerAttributes) reader.ReadUInt32();
+
+ // TODO: member pointer info
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetBaseType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Pointer {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}.");
+
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs
new file mode 100644
index 000000000..90465a8a9
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs
@@ -0,0 +1,52 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedProcedureTypeRecord : ProcedureTypeRecord
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _returnTypeIndex;
+ private readonly ushort _parameterCount;
+ private readonly uint _argumentListIndex;
+
+ ///
+ /// Reads a procedure type from the provided input stream.
+ ///
+ /// The reading context in which the type is situated in.
+ /// The index to assign to the type.
+ /// The input stream to read from.
+ public SerializedProcedureTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _returnTypeIndex = reader.ReadUInt32();
+ CallingConvention = (CodeViewCallingConvention) reader.ReadByte();
+ Attributes = (MemberFunctionAttributes) reader.ReadByte();
+ _parameterCount = reader.ReadUInt16();
+ _argumentListIndex = reader.ReadUInt32();
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetReturnType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Procedure type {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}.");
+ }
+
+ ///
+ protected override ArgumentListLeaf? GetArguments()
+ {
+ if (_argumentListIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list
+ ? list
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Procedure type {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs
new file mode 100644
index 000000000..4196ac5c9
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs
@@ -0,0 +1,45 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedStaticDataField : StaticDataField
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _dataTypeIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads a static data member list from the provided input stream.
+ ///
+ /// The reading context in which the member is situated in.
+ /// The type index to assign to the member.
+ /// The input stream to read from.
+ public SerializedStaticDataField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ Attributes = (CodeViewFieldAttributes) reader.ReadUInt16();
+ _dataTypeIndex = reader.ReadUInt32();
+
+ _context = context;
+ _nameReader = reader;
+ reader.AdvanceUntil(0, true);
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override CodeViewTypeRecord? GetDataType()
+ {
+ if (_dataTypeIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Static data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs
new file mode 100644
index 000000000..3a0169a39
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs
@@ -0,0 +1,55 @@
+using System;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedUnionTypeRecord : UnionTypeRecord
+{
+ private readonly PdbReaderContext _context;
+ private readonly ushort _memberCount;
+ private readonly uint _fieldIndex;
+ private readonly BinaryStreamReader _nameReader;
+ private readonly BinaryStreamReader _uniqueNameReader;
+
+ ///
+ /// Reads a union type from the provided input stream.
+ ///
+ /// The reading context in which the type is situated in.
+ /// The index to assign to the type.
+ /// The input stream to read from.
+ public SerializedUnionTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ _memberCount = reader.ReadUInt16();
+ StructureAttributes = (StructureAttributes) reader.ReadUInt16();
+ _fieldIndex = reader.ReadUInt32();
+
+ Size = Convert.ToUInt64(ReadNumeric(ref reader));
+
+ _nameReader = reader.Fork();
+ reader.AdvanceUntil(0, true);
+ _uniqueNameReader = reader.Fork();
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override FieldListLeaf? GetFields()
+ {
+ if (_fieldIndex == 0)
+ return null;
+
+ return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list
+ ? list
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Union type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs
new file mode 100644
index 000000000..5be9b1165
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs
@@ -0,0 +1,55 @@
+using System;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedVBaseClassField : VBaseClassField
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _baseTypeIndex;
+ private readonly uint _basePointerIndex;
+
+ ///
+ /// Reads a virtual base class from the provided input stream.
+ ///
+ /// The reading context in which the class is situated in.
+ /// The type index to assign to the type.
+ /// The input stream to read from.
+ /// true if the field is an indirect virtual base class, false otherwise.
+ public SerializedVBaseClassField(
+ PdbReaderContext context,
+ uint typeIndex,
+ ref BinaryStreamReader reader,
+ bool isIndirect)
+ : base(typeIndex)
+ {
+ _context = context;
+ Attributes = (CodeViewFieldAttributes) reader.ReadUInt16();
+ _baseTypeIndex = reader.ReadUInt32();
+ _basePointerIndex = reader.ReadUInt32();
+ PointerOffset = Convert.ToUInt64(ReadNumeric(ref reader));
+ TableOffset = Convert.ToUInt64(ReadNumeric(ref reader));
+ IsIndirect = isIndirect;
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetBaseType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Virtual base class {TypeIndex:X8} contains an invalid base type index {_baseTypeIndex:X8}.");
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetBasePointerType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_basePointerIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Virtual base class {TypeIndex:X8} contains an invalid base pointer type index {_basePointerIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs
new file mode 100644
index 000000000..66ed003c7
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs
@@ -0,0 +1,35 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedVTableField : VTableField
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _pointerTypeIndex;
+
+ ///
+ /// Reads a virtual function table field from the provided input stream.
+ ///
+ /// The reading context in which the field is situated in.
+ /// The type index to assign to the field.
+ /// The input stream to read from.
+ public SerializedVTableField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _context = context;
+ reader.ReadUInt16(); // padding
+ _pointerTypeIndex = reader.ReadUInt32();
+ }
+
+ ///
+ protected override CodeViewTypeRecord? GetPointerType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_pointerTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Virtual function table type {TypeIndex:X8} contains an invalid pointer type index {_pointerTypeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs
new file mode 100644
index 000000000..b8b7f0396
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Leaves.Serialized;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedVTableShapeLeaf : VTableShapeLeaf
+{
+ private readonly ushort _count;
+ private readonly BinaryStreamReader _entriesReader;
+
+ ///
+ /// Reads a virtual function table shape from the provided input stream.
+ ///
+ /// The reading context in which the shape is situated in.
+ /// The index to assign to the shape.
+ /// The input stream to read from.
+ public SerializedVTableShapeLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader)
+ : base(typeIndex)
+ {
+ _count = reader.ReadUInt16();
+ _entriesReader = reader;
+ }
+
+ ///
+ protected override IList GetEntries()
+ {
+ var result = new List(_count);
+ var reader = _entriesReader.Fork();
+
+ // Entries are stored as 4-bit values.
+ byte currentByte = 0;
+ for (int i = 0; i < _count; i++)
+ {
+ if (i % 2 == 0)
+ {
+ currentByte = reader.ReadByte();
+ result.Add((VTableShapeEntry) (currentByte & 0xF));
+ }
+ else
+ {
+ result.Add((VTableShapeEntry) ((currentByte >> 4) & 0xF));
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs
new file mode 100644
index 000000000..77cbbff33
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs
@@ -0,0 +1,250 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all basic type kinds that can be used as a type index.
+///
+///
+/// Reference: https://llvm.org/docs/PDB/TpiStream.html
+///
+public enum SimpleTypeKind : uint
+{
+ ///
+ /// Indicates the type index indicates the absence of a specific type or type category.
+ ///
+ None = 0x0000,
+
+ ///
+ /// Indicates the type index references the void type.
+ ///
+ Void = 0x0003,
+
+ ///
+ /// Indicates the type index references a type that is not translated by CVPack.
+ ///
+ NotTranslated = 0x0007,
+
+ ///
+ /// Indicates the type index references the OLE/COM HRESULT type.
+ ///
+ HResult = 0x0008,
+
+ ///
+ /// Indicates the type index references the 8 bit signed character type.
+ ///
+ SignedCharacter = 0x0010,
+
+ ///
+ /// Indicates the type index references the 8 bit unsigned character type.
+ ///
+ UnsignedCharacter = 0x0020,
+
+ ///
+ /// Indicates the type index references the narrow character type.
+ ///
+ NarrowCharacter = 0x0070,
+
+ ///
+ /// Indicates the type index references the wide character type.
+ ///
+ WideCharacter = 0x0071,
+
+ ///
+ /// Indicates the type index references the char16_t type.
+ ///
+ Character16 = 0x007a,
+
+ ///
+ /// Indicates the type index references the char32_t type.
+ ///
+ Character32 = 0x007b,
+
+ ///
+ /// Indicates the type index references the char8_t type.
+ ///
+ Character8 = 0x007c,
+
+ ///
+ /// Indicates the type index references the 8 bit signed int type.
+ ///
+ SByte = 0x0068,
+
+ ///
+ /// Indicates the type index references the 8 bit unsigned int type.
+ ///
+ Byte = 0x0069,
+
+ ///
+ /// Indicates the type index references the 16 bit signed type.
+ ///
+ Int16Short = 0x0011,
+
+ ///
+ /// Indicates the type index references the 16 bit unsigned type.
+ ///
+ UInt16Short = 0x0021,
+
+ ///
+ /// Indicates the type index references the 16 bit signed int type.
+ ///
+ Int16 = 0x0072,
+
+ ///
+ /// Indicates the type index references the 16 bit unsigned int type.
+ ///
+ UInt16 = 0x0073,
+
+ ///
+ /// Indicates the type index references the 32 bit signed type.
+ ///
+ Int32Long = 0x0012,
+
+ ///
+ /// Indicates the type index references the 32 bit unsigned type.
+ ///
+ UInt32Long = 0x0022,
+
+ ///
+ /// Indicates the type index references the 32 bit signed int type.
+ ///
+ Int32 = 0x0074,
+
+ ///
+ /// Indicates the type index references the 32 bit unsigned int type.
+ ///
+ UInt32 = 0x0075,
+
+ ///
+ /// Indicates the type index references the 64 bit signed type.
+ ///
+ Int64Quad = 0x0013,
+
+ ///
+ /// Indicates the type index references the 64 bit unsigned type.
+ ///
+ UInt64Quad = 0x0023,
+
+ ///
+ /// Indicates the type index references the 64 bit signed int type.
+ ///
+ Int64 = 0x0076,
+
+ ///
+ /// Indicates the type index references the 64 bit unsigned int type.
+ ///
+ UInt64 = 0x0077,
+
+ ///
+ /// Indicates the type index references the 128 bit signed int type.
+ ///
+ Int128Oct = 0x0014,
+
+ ///
+ /// Indicates the type index references the 128 bit unsigned int type.
+ ///
+ UInt128Oct = 0x0024,
+
+ ///
+ /// Indicates the type index references the 128 bit signed int type.
+ ///
+ Int128 = 0x0078,
+
+ ///
+ /// Indicates the type index references the 128 bit unsigned int type.
+ ///
+ UInt128 = 0x0079,
+
+ ///
+ /// Indicates the type index references the 16 bit real type.
+ ///
+ Float16 = 0x0046,
+
+ ///
+ /// Indicates the type index references the 32 bit real type.
+ ///
+ Float32 = 0x0040,
+
+ ///
+ /// Indicates the type index references the 32 bit PP real type.
+ ///
+ Float32PartialPrecision = 0x0045,
+
+ ///
+ /// Indicates the type index references the 48 bit real type.
+ ///
+ Float48 = 0x0044,
+
+ ///
+ /// Indicates the type index references the 64 bit real type.
+ ///
+ Float64 = 0x0041,
+
+ ///
+ /// Indicates the type index references the 80 bit real type.
+ ///
+ Float80 = 0x0042,
+
+ ///
+ /// Indicates the type index references the 128 bit real type.
+ ///
+ Float128 = 0x0043,
+
+ ///
+ /// Indicates the type index references the 16 bit complex type.
+ ///
+ Complex16 = 0x0056,
+
+ ///
+ /// Indicates the type index references the 32 bit complex type.
+ ///
+ Complex32 = 0x0050,
+
+ ///
+ /// Indicates the type index references the 32 bit PP complex type.
+ ///
+ Complex32PartialPrecision = 0x0055,
+
+ ///
+ /// Indicates the type index references the 48 bit complex type.
+ ///
+ Complex48 = 0x0054,
+
+ ///
+ /// Indicates the type index references the 64 bit complex type.
+ ///
+ Complex64 = 0x0051,
+
+ ///
+ /// Indicates the type index references the 80 bit complex type.
+ ///
+ Complex80 = 0x0052,
+
+ ///
+ /// Indicates the type index references the 128 bit complex type.
+ ///
+ Complex128 = 0x0053,
+
+ ///
+ /// Indicates the type index references the 8 bit boolean type.
+ ///
+ Boolean8 = 0x0030,
+
+ ///
+ /// Indicates the type index references the 16 bit boolean type.
+ ///
+ Boolean16 = 0x0031,
+
+ ///
+ /// Indicates the type index references the 32 bit boolean type.
+ ///
+ Boolean32 = 0x0032,
+
+ ///
+ /// Indicates the type index references the 64 bit boolean type.
+ ///
+ Boolean64 = 0x0033,
+
+ ///
+ /// Indicates the type index references the 128 bit boolean type.
+ ///
+ Boolean128 = 0x0034,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs
new file mode 100644
index 000000000..7dff91423
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs
@@ -0,0 +1,50 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all possible modes that a simple type in a PDB image can be set to.
+///
+///
+/// Reference: https://llvm.org/docs/PDB/TpiStream.html
+///
+public enum SimpleTypeMode
+{
+ ///
+ /// Indicates the type is not a pointer.
+ ///
+ Direct = 0,
+
+ ///
+ /// Indicates the type is a near pointer.
+ ///
+ NearPointer = 1,
+
+ ///
+ /// Indicates the type is a far pointer.
+ ///
+ FarPointer = 2,
+
+ ///
+ /// Indicates the type is a huge pointer.
+ ///
+ HugePointer = 3,
+
+ ///
+ /// Indicates the type is a 32 bit near pointer.
+ ///
+ NearPointer32 = 4,
+
+ ///
+ /// Indicates the type is a 32 bit far pointer.
+ ///
+ FarPointer32 = 5,
+
+ ///
+ /// Indicates the type is a 64 bit near pointer.
+ ///
+ NearPointer64 = 6,
+
+ ///
+ /// Indicates the type is a 128 bit near pointer.
+ ///
+ NearPointer128 = 7,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs
new file mode 100644
index 000000000..8b36de431
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs
@@ -0,0 +1,60 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a simple type referenced by a simple type index.
+///
+public class SimpleTypeRecord : CodeViewTypeRecord
+{
+ ///
+ /// Constructs a new simple type based on the provided type index.
+ ///
+ /// The type index.
+ public SimpleTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Constructs a new simple type with the provided type kind.
+ ///
+ /// The type kind.
+ public SimpleTypeRecord(SimpleTypeKind kind)
+ : base((uint) kind)
+ {
+ }
+
+ ///
+ /// Constructs a new simple type with the provided type kind and mode.
+ ///
+ /// The type kind.
+ /// The mode indicating the pointer specifiers added to the type.
+ public SimpleTypeRecord(SimpleTypeKind kind, SimpleTypeMode mode)
+ : base((uint) kind | ((uint) mode << 8))
+ {
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.SimpleType;
+
+ ///
+ /// Gets the kind of the simple type.
+ ///
+ public SimpleTypeKind Kind => (SimpleTypeKind) (TypeIndex & 0b1111_1111);
+
+ ///
+ /// Gets the mode describing the pointer specifiers that are added to the simple type.
+ ///
+ public SimpleTypeMode Mode => (SimpleTypeMode) ((TypeIndex >> 8) & 0b1111);
+
+ ///
+ /// Gets a value indicating whether the type is a pointer or not.
+ ///
+ public bool IsPointer => Mode != SimpleTypeMode.Direct;
+
+ ///
+ public override string ToString() => Mode == SimpleTypeMode.Direct
+ ? Kind.ToString()
+ : $"{Kind}*";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs
new file mode 100644
index 000000000..b06398da4
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs
@@ -0,0 +1,29 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a static data member in a class or structure type.
+///
+public class StaticDataField : CodeViewDataField
+{
+ ///
+ /// Initializes an empty static data field.
+ ///
+ /// The type index to assign to the field.
+ protected StaticDataField(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Creates a new static data member.
+ ///
+ /// The name.
+ /// The data type of the member.
+ public StaticDataField(Utf8String name, CodeViewTypeRecord dataType)
+ : base(dataType, name)
+ {
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.StMember;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs
new file mode 100644
index 000000000..64f582c6b
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs
@@ -0,0 +1,80 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all possible attributes that can be assigned to a structure, class or enum type symbol.
+///
+[Flags]
+public enum StructureAttributes : ushort
+{
+ ///
+ /// Indicates the structure is packed.
+ ///
+ Packed = 0b00000000_00000001,
+
+ ///
+ /// Indicates the structure defines constructors or destructors.
+ ///
+ Ctor = 0b00000000_00000010,
+
+ ///
+ /// Indicates the structure defines overloaded operators.
+ ///
+ OvlOps = 0b00000000_00000100,
+
+ ///
+ /// Indicates the structure is a nested class.
+ ///
+ IsNested = 0b00000000_00001000,
+
+ ///
+ /// Indicates the structure defines nested types.
+ ///
+ CNested = 0b00000000_00010000,
+
+ ///
+ /// Indicates the structure defines an overloaded assignment (=) operator.
+ ///
+ OpAssign = 0b00000000_00100000,
+
+ ///
+ /// Indicates the structure defines casting methods.
+ ///
+ OpCast = 0b00000000_01000000,
+
+ ///
+ /// Indicates the structure true is a forward reference.
+ ///
+ FwdRef = 0b00000000_10000000,
+
+ ///
+ /// Indicates the structure is a scoped definition.
+ ///
+ Scoped = 0b00000001_00000000,
+
+ ///
+ /// Indicates the structure has a decorated name following the regular naming conventions.
+ ///
+ HasUniqueName = 0b00000010_00000000,
+
+ ///
+ /// Indicates the structure cannot be used as a base class.
+ ///
+ Sealed = 0b00000100_00000000,
+
+ ///
+ /// Defines the mask for the floating point type that is used within this structure.
+ ///
+ HfaMask = 0b00011000_00000000,
+
+ ///
+ /// Indicates the structure is an intrinsic type.
+ ///
+ Intrinsic = 0b00100000_00000000,
+
+ ///
+ /// Defines the mask for the MoCOM type kind.
+ ///
+ MoComMask = 0b11000000_00000000,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs
new file mode 100644
index 000000000..a28d73ceb
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs
@@ -0,0 +1,70 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a type of a value that may have several representations or formats within the same position of memory.
+///
+public class UnionTypeRecord : CodeViewCompositeTypeRecord
+{
+ private readonly LazyVariable _uniqueName;
+
+ ///
+ /// Initializes an empty union type.
+ ///
+ /// The type index to assign to the union.
+ protected UnionTypeRecord(uint typeIndex)
+ : base(typeIndex)
+ {
+ _uniqueName = new LazyVariable(GetUniqueName);
+ }
+
+ ///
+ /// Creates a new union type with the provided size.
+ ///
+ /// The total size in bytes of the union.
+ public UnionTypeRecord(ulong size)
+ : base(0)
+ {
+ _uniqueName = new LazyVariable(Utf8String.Empty);
+ Size = size;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Union;
+
+ ///
+ /// Gets or sets the total size in bytes of the union type.
+ ///
+ public ulong Size
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the uniquely identifiable name for this type.
+ ///
+ public Utf8String UniqueName
+ {
+ get => _uniqueName.Value;
+ set => _uniqueName.Value = value;
+ }
+
+ ///
+ /// Obtains the uniquely identifiable name of the type.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual Utf8String? GetUniqueName() => null;
+
+ ///
+ public override string ToString()
+ {
+ if (!Utf8String.IsNullOrEmpty(Name))
+ return Name;
+ if (!Utf8String.IsNullOrEmpty(UniqueName))
+ return UniqueName;
+ return $"";
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs
new file mode 100644
index 000000000..7fd7898f1
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs
@@ -0,0 +1,47 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents an unknown or unsupported CodeView type record.
+///
+public class UnknownCodeViewLeaf : CodeViewLeaf
+{
+ ///
+ /// Creates a new unknown type record.
+ ///
+ /// The type of symbol.
+ /// The raw data stored in the record.
+ public UnknownCodeViewLeaf(CodeViewLeafKind leafKind, byte[] data)
+ : this(0, leafKind, data)
+ {
+ }
+
+ ///
+ /// Creates a new unknown type record.
+ ///
+ /// The type index to assign to the type
+ /// The type of symbol.
+ /// The raw data stored in the record.
+ internal UnknownCodeViewLeaf(uint typeIndex, CodeViewLeafKind leafKind, byte[] data)
+ : base(typeIndex)
+ {
+ LeafKind = leafKind;
+ Data = data;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind
+ {
+ get;
+ }
+
+ ///
+ /// Gets the raw data stored in the record.
+ ///
+ public byte[] Data
+ {
+ get;
+ }
+
+ ///
+ public override string ToString() => $"{LeafKind.ToString()} ({Data.Length.ToString()} bytes)";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs
new file mode 100644
index 000000000..99d5edf0e
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs
@@ -0,0 +1,115 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents a direct or indirect reference to a virtual base class object in a structure.
+///
+public class VBaseClassField : CodeViewField
+{
+ private readonly LazyVariable _baseType;
+ private readonly LazyVariable _basePointerType;
+
+ ///
+ /// Initializes a new empty virtual base class field.
+ ///
+ /// The type index to assign to the field.
+ protected VBaseClassField(uint typeIndex)
+ : base(typeIndex)
+ {
+ _baseType = new LazyVariable(GetBaseType);
+ _basePointerType = new LazyVariable(GetBasePointerType);
+ }
+
+ ///
+ /// Creates a new virtual base class field.
+ ///
+ /// The type to reference as base type.
+ /// The type of the virtual base pointer.
+ /// The offset of the virtual base pointer
+ /// The offset from the base table.
+ /// true if the field is an indirect virtual base class, false otherwise.
+ public VBaseClassField(
+ CodeViewTypeRecord baseType,
+ CodeViewTypeRecord pointerType,
+ ulong pointerOffset,
+ ulong tableOffset,
+ bool isIndirect)
+ : base(0)
+ {
+ _baseType = new LazyVariable(baseType);
+ _basePointerType = new LazyVariable(pointerType);
+ PointerOffset = pointerOffset;
+ TableOffset = tableOffset;
+ IsIndirect = isIndirect;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the virtual base class is an indirect base class.
+ ///
+ public bool IsIndirect
+ {
+ get;
+ set;
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => IsIndirect
+ ? CodeViewLeafKind.IVBClass
+ : CodeViewLeafKind.VBClass;
+
+ ///
+ /// Gets or sets the base type that this base class is referencing.
+ ///
+ public CodeViewTypeRecord? Type
+ {
+ get => _baseType.Value;
+ set => _baseType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the type of the base pointer that this base class uses.
+ ///
+ public CodeViewTypeRecord? PointerType
+ {
+ get => _basePointerType.Value;
+ set => _basePointerType.Value = value;
+ }
+
+ ///
+ /// Gets or sets the virtual base pointer offset relative to the address point.
+ ///
+ public ulong PointerOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the virtual base pointer offset relative to the virtual base table.
+ ///
+ public ulong TableOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Obtains the base type that the class is referencing.
+ ///
+ /// The base type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetBaseType() => null;
+
+ ///
+ /// Obtains the type of the base pointer that the class is uses.
+ ///
+ /// The base pointer type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetBasePointerType() => null;
+
+ ///
+ public override string ToString() => Type?.ToString() ?? "<<>>>";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs
new file mode 100644
index 000000000..11355cad4
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs
@@ -0,0 +1,50 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Represents the virtual function table field in a class.
+///
+public class VTableField : CodeViewField
+{
+ private readonly LazyVariable _type;
+
+ ///
+ /// Initializes an empty virtual function table field.
+ ///
+ /// The type index to assign to the type.
+ protected VTableField(uint typeIndex)
+ : base(typeIndex)
+ {
+ _type = new LazyVariable(GetPointerType);
+ }
+
+ ///
+ /// Creates a new virtual function table field.
+ ///
+ /// The pointer type to use.
+ public VTableField(CodeViewTypeRecord pointerType)
+ : base(0)
+ {
+ _type = new LazyVariable(pointerType);
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.VFuncTab;
+
+ ///
+ /// Gets or sets the pointer type of the virtual function table.
+ ///
+ public CodeViewTypeRecord? PointerType
+ {
+ get => _type.Value;
+ set => _type.Value = value;
+ }
+
+ ///
+ /// Obtains the pointer type that the virtual function table type.
+ ///
+ /// The pointer type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetPointerType() => null;
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs
new file mode 100644
index 000000000..abbb9bba9
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs
@@ -0,0 +1,17 @@
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Provides members defining all possible types that a single entry in a can be.
+///
+public enum VTableShapeEntry : byte
+{
+#pragma warning disable CS1591
+ Near = 0x00,
+ Far = 0x01,
+ Thin = 0x02,
+ Outer = 0x03,
+ Meta = 0x04,
+ Near32 = 0x05,
+ Far32 = 0x06,
+#pragma warning restore CS1591
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs
new file mode 100644
index 000000000..508664c32
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.Threading;
+
+namespace AsmResolver.Symbols.Pdb.Leaves;
+
+///
+/// Describes the shape of the virtual function table of a class or structure type.
+///
+public class VTableShapeLeaf : CodeViewLeaf
+{
+ private IList? _entries;
+
+ ///
+ /// Initializes a new empty virtual function table shape.
+ ///
+ /// The type index to assign to the shape.
+ protected VTableShapeLeaf(uint typeIndex)
+ : base(typeIndex)
+ {
+ }
+
+ ///
+ /// Creates a new empty virtual function table shape.
+ ///
+ public VTableShapeLeaf()
+ : base(0)
+ {
+ }
+
+ ///
+ /// Creates a new virtual function table shape with the provided entries.
+ ///
+ public VTableShapeLeaf(params VTableShapeEntry[] entries)
+ : base(0)
+ {
+ _entries = new List(entries);
+ }
+
+ ///
+ public override CodeViewLeafKind LeafKind => CodeViewLeafKind.VTShape;
+
+ ///
+ /// Gets the list of entries that defines the shape of the virtual function table.
+ ///
+ public IList Entries
+ {
+ get
+ {
+ if (_entries is null)
+ Interlocked.CompareExchange(ref _entries, GetEntries(), null);
+ return _entries;
+ }
+ }
+
+ ///
+ /// Obtains the list of entries stored in the shape.
+ ///
+ /// The entries.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetEntries() => new List();
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs
new file mode 100644
index 000000000..3fb37697a
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Provides members defining all attributes that can be assigned to a single DBI stream.
+///
+[Flags]
+public enum DbiAttributes : ushort
+{
+ ///
+ /// Indicates no attributes were assigned.
+ ///
+ None = 0,
+
+ ///
+ /// Indicates the program was linked in an incremental manner.
+ ///
+ IncrementallyLinked = 1,
+
+ ///
+ /// Indicates private symbols were stripped from the PDB file.
+ ///
+ PrivateSymbolsStripped = 2,
+
+ ///
+ /// Indicates the program was linked using link.exe with the undocumented /DEBUG:CTYPES flag.
+ ///
+ HasConflictingTypes = 4,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs
new file mode 100644
index 000000000..247bd21ac
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs
@@ -0,0 +1,595 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using AsmResolver.IO;
+using AsmResolver.PE.File.Headers;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Represents the DBI Stream (also known as the Debug Information stream).
+///
+public class DbiStream : SegmentBase
+{
+ ///
+ /// Gets the default fixed MSF stream index for the DBI stream.
+ ///
+ public const int StreamIndex = 3;
+
+ private IList? _modules;
+ private IList? _sectionContributions;
+ private IList? _sectionMaps;
+ private readonly LazyVariable _typeServerMapStream;
+ private readonly LazyVariable _ecStream;
+ private IList? _sourceFiles;
+ private IList? _extraStreamIndices;
+
+ ///
+ /// Creates a new empty DBI stream.
+ ///
+ public DbiStream()
+ {
+ _typeServerMapStream = new LazyVariable(GetTypeServerMapStream);
+ _ecStream = new LazyVariable(GetECStream);
+ IsNewVersionFormat = true;
+ }
+
+ ///
+ /// Gets or sets the version signature assigned to the DBI stream.
+ ///
+ ///
+ /// This value should always be -1 for valid PDB files.
+ ///
+ public int VersionSignature
+ {
+ get;
+ set;
+ } = -1;
+
+ ///
+ /// Gets or sets the version number of the DBI header.
+ ///
+ ///
+ /// Modern tooling only recognize the VC7.0 file format.
+ ///
+ public DbiStreamVersion VersionHeader
+ {
+ get;
+ set;
+ } = DbiStreamVersion.V70;
+
+ ///
+ /// Gets or sets the number of times the DBI stream has been written.
+ ///
+ public uint Age
+ {
+ get;
+ set;
+ } = 1;
+
+ ///
+ /// Gets or sets the MSF stream index of the Global Symbol Stream.
+ ///
+ public ushort GlobalStreamIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets a bitfield containing the major and minor version of the toolchain that was used to build the program.
+ ///
+ public ushort BuildNumber
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets a value indicating that the DBI stream is using the new file format (NewDBI).
+ ///
+ public bool IsNewVersionFormat
+ {
+ get => (BuildNumber & 0x8000) != 0;
+ set => BuildNumber = (ushort) ((BuildNumber & ~0x8000) | (value ? 0x8000 : 0));
+ }
+
+ ///
+ /// Gets or sets the major version of the toolchain that was used to build the program.
+ ///
+ public byte BuildMajorVersion
+ {
+ get => (byte) ((BuildNumber >> 8) & 0x7F);
+ set => BuildNumber = (ushort) ((BuildNumber & ~0x7F00) | (value << 8));
+ }
+
+ ///
+ /// Gets or sets the minor version of the toolchain that was used to build the program.
+ ///
+ public byte BuildMinorVersion
+ {
+ get => (byte) (BuildNumber & 0xFF);
+ set => BuildNumber = (ushort) ((BuildNumber & ~0x00FF) | value);
+ }
+
+ ///
+ /// Gets or sets the MSF stream index of the Public Symbol Stream.
+ ///
+ public ushort PublicStreamIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the version number of mspdbXXXX.dll that was used to produce this PDB file.
+ ///
+ public ushort PdbDllVersion
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the MSF stream index of the Symbol Record Stream.
+ ///
+ public ushort SymbolRecordStreamIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Unknown.
+ ///
+ public ushort PdbDllRbld
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the MSF stream index of the MFC type server.
+ ///
+ public uint MfcTypeServerIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets attributes associated to the DBI stream.
+ ///
+ public DbiAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the machine type the program was compiled for.
+ ///
+ public MachineType Machine
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets a collection of modules (object files) that were linked together into the program.
+ ///
+ public IList Modules
+ {
+ get
+ {
+ if (_modules is null)
+ Interlocked.CompareExchange(ref _modules, GetModules(), null);
+ return _modules;
+ }
+ }
+
+ ///
+ /// Gets a collection of section contributions describing the layout of the sections of the final executable file.
+ ///
+ public IList SectionContributions
+ {
+ get
+ {
+ if (_sectionContributions is null)
+ Interlocked.CompareExchange(ref _sectionContributions, GetSectionContributions(), null);
+ return _sectionContributions;
+ }
+ }
+
+ ///
+ /// Gets a collection of section mappings stored in the section mapping sub stream.
+ ///
+ ///
+ /// The exact purpose of this is unknown, but it seems to be always containing a copy of the sections in the final
+ /// executable file.
+ ///
+ public IList SectionMaps
+ {
+ get
+ {
+ if (_sectionMaps is null)
+ Interlocked.CompareExchange(ref _sectionMaps, GetSectionMaps(), null);
+ return _sectionMaps;
+ }
+ }
+
+ ///
+ /// Gets or sets the contents of the type server map sub stream.
+ ///
+ ///
+ /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as
+ /// a raw segment.
+ ///
+ public ISegment? TypeServerMapStream
+ {
+ get => _typeServerMapStream.Value;
+ set => _typeServerMapStream.Value = value;
+ }
+
+ ///
+ /// Gets or sets the contents of the Edit-and-Continue sub stream.
+ ///
+ ///
+ /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as
+ /// a raw segment.
+ ///
+ public ISegment? ECStream
+ {
+ get => _ecStream.Value;
+ set => _ecStream.Value = value;
+ }
+
+ ///
+ /// Gets a collection of source files assigned to each module in .
+ ///
+ ///
+ /// Every collection of source files within this list corresponds to exactly the module within
+ /// at the same index. For example, the first source file list in this collection is the source file list of the
+ /// first module.
+ ///
+ public IList SourceFiles
+ {
+ get
+ {
+ if (_sourceFiles is null)
+ Interlocked.CompareExchange(ref _sourceFiles, GetSourceFiles(), null);
+ return _sourceFiles;
+ }
+ }
+
+ ///
+ /// Gets a collection of indices referring to additional debug streams in the MSF file.
+ ///
+ public IList ExtraStreamIndices
+ {
+ get
+ {
+ if (_extraStreamIndices is null)
+ Interlocked.CompareExchange(ref _extraStreamIndices, GetExtraStreamIndices(), null);
+ return _extraStreamIndices;
+ }
+ }
+
+ ///
+ /// Reads a single DBI stream from the provided input stream.
+ ///
+ /// The input stream.
+ /// The parsed DBI stream.
+ public static DbiStream FromReader(BinaryStreamReader reader) => new SerializedDbiStream(reader);
+
+ ///
+ /// Obtains the list of module descriptors.
+ ///
+ /// The module descriptors
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetModules() => new List();
+
+ ///
+ /// Obtains the list of section contributions.
+ ///
+ /// The section contributions.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetSectionContributions() => new List();
+
+ ///
+ /// Obtains the list of section maps.
+ ///
+ /// The section maps.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetSectionMaps() => new List();
+
+ ///
+ /// Obtains the contents of the type server map sub stream.
+ ///
+ /// The contents of the sub stream.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual ISegment? GetTypeServerMapStream() => null;
+
+ ///
+ /// Obtains the contents of the EC sub stream.
+ ///
+ /// The contents of the sub stream.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual ISegment? GetECStream() => null;
+
+ ///
+ /// Obtains a table that assigns a list of source files to every module referenced in the PDB file.
+ ///
+ /// The table.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetSourceFiles() => new List();
+
+ ///
+ /// Obtains the list of indices referring to additional debug streams in the MSF file.
+ ///
+ /// The list of indices.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetExtraStreamIndices() => new List();
+
+ ///
+ public override uint GetPhysicalSize()
+ {
+ return GetHeaderSize()
+ + GetModuleStreamSize()
+ + GetSectionContributionStreamSize()
+ + GetSectionMapStreamSize()
+ + GetSourceInfoStreamSize()
+ + GetTypeServerMapStreamSize()
+ + GetECStreamSize()
+ + GetOptionalDebugStreamSize()
+ ;
+ }
+
+ private static uint GetHeaderSize()
+ {
+ return sizeof(int) // VersionSignature
+ + sizeof(DbiStreamVersion) // VersionHeader
+ + sizeof(uint) // Age
+ + sizeof(ushort) // GlobalStreamIndex
+ + sizeof(ushort) // BuildNumber
+ + sizeof(ushort) // PublicStreamIndex
+ + sizeof(ushort) // PdbDllVersion
+ + sizeof(ushort) // SymbolRecordStreamIndex
+ + sizeof(ushort) // PdbDllRbld
+ + sizeof(uint) // ModuleInfoSize
+ + sizeof(uint) // SectionContributionSize
+ + sizeof(uint) // SectionMapSize
+ + sizeof(uint) // SourceInfoSize
+ + sizeof(uint) // TypeServerMapSize
+ + sizeof(uint) // MfcTypeServerIndex
+ + sizeof(uint) // OptionalDebugStreamSize
+ + sizeof(uint) // ECStreamSize
+ + sizeof(DbiAttributes) // Attributes
+ + sizeof(MachineType) // MachineType
+ + sizeof(uint) // Padding
+ ;
+ }
+
+ private uint GetModuleStreamSize()
+ {
+ return ((uint) Modules.Sum(m => m.GetPhysicalSize())).Align(sizeof(uint));
+ }
+
+ private uint GetSectionContributionStreamSize()
+ {
+ return sizeof(uint) // version
+ + SectionContribution.EntrySize * (uint) SectionContributions.Count;
+ }
+
+ private uint GetSectionMapStreamSize()
+ {
+ return sizeof(ushort) // Count
+ + sizeof(ushort) // LogCount
+ + SectionMap.EntrySize * (uint) SectionMaps.Count;
+ }
+
+ private uint GetSourceInfoStreamSize()
+ {
+ uint totalFileCount = 0;
+ uint nameBufferSize = 0;
+ var stringOffsets = new Dictionary();
+
+ // Simulate the construction of name buffer
+ for (int i = 0; i < SourceFiles.Count; i++)
+ {
+ var collection = SourceFiles[i];
+ totalFileCount += (uint) collection.Count;
+
+ for (int j = 0; j < collection.Count; j++)
+ {
+ // If name is not added yet, "append" to the name buffer.
+ var name = collection[j];
+ if (!stringOffsets.ContainsKey(name))
+ {
+ stringOffsets[name] = nameBufferSize;
+ nameBufferSize += (uint) name.ByteCount + 1u;
+ }
+ }
+ }
+
+ return (sizeof(ushort) // ModuleCount
+ + sizeof(ushort) // SourceFileCount
+ + sizeof(ushort) * (uint) SourceFiles.Count // ModuleIndices
+ + sizeof(ushort) * (uint) SourceFiles.Count // SourceFileCounts
+ + sizeof(uint) * totalFileCount // NameOffsets
+ + nameBufferSize // NameBuffer
+ ).Align(4);
+ }
+
+ private uint GetTypeServerMapStreamSize()
+ {
+ return TypeServerMapStream?.GetPhysicalSize().Align(sizeof(uint)) ?? 0u;
+ }
+
+ private uint GetOptionalDebugStreamSize()
+ {
+ return (uint) (ExtraStreamIndices.Count * sizeof(ushort));
+ }
+
+ private uint GetECStreamSize()
+ {
+ return ECStream?.GetPhysicalSize() ?? 0u;
+ }
+
+ ///
+ public override void Write(IBinaryStreamWriter writer)
+ {
+ WriteHeader(writer);
+ WriteModuleStream(writer);
+ WriteSectionContributionStream(writer);
+ WriteSectionMapStream(writer);
+ WriteSourceInfoStream(writer);
+ WriteTypeServerMapStream(writer);
+ WriteECStream(writer);
+ WriteOptionalDebugStream(writer);
+ }
+
+ private void WriteHeader(IBinaryStreamWriter writer)
+ {
+ writer.WriteInt32(VersionSignature);
+ writer.WriteUInt32((uint) VersionHeader);
+ writer.WriteUInt32(Age);
+ writer.WriteUInt16(GlobalStreamIndex);
+ writer.WriteUInt16(BuildNumber);
+ writer.WriteUInt16(PublicStreamIndex);
+ writer.WriteUInt16(PdbDllVersion);
+ writer.WriteUInt16(SymbolRecordStreamIndex);
+ writer.WriteUInt16(PdbDllRbld);
+
+ writer.WriteUInt32(GetModuleStreamSize());
+ writer.WriteUInt32(GetSectionContributionStreamSize());
+ writer.WriteUInt32(GetSectionMapStreamSize());
+ writer.WriteUInt32(GetSourceInfoStreamSize());
+ writer.WriteUInt32(GetTypeServerMapStreamSize());
+
+ writer.WriteUInt32(MfcTypeServerIndex);
+
+ writer.WriteUInt32(GetOptionalDebugStreamSize());
+ writer.WriteUInt32(GetECStreamSize());
+
+ writer.WriteUInt16((ushort) Attributes);
+ writer.WriteUInt16((ushort) Machine);
+
+ writer.WriteUInt32(0);
+ }
+
+ private void WriteModuleStream(IBinaryStreamWriter writer)
+ {
+ var modules = Modules;
+ for (int i = 0; i < modules.Count; i++)
+ modules[i].Write(writer);
+
+ writer.Align(sizeof(uint));
+ }
+
+ private void WriteSectionContributionStream(IBinaryStreamWriter writer)
+ {
+ // TODO: make customizable
+ writer.WriteUInt32((uint) SectionContributionStreamVersion.Ver60);
+
+ var contributions = SectionContributions;
+ for (int i = 0; i < contributions.Count; i++)
+ contributions[i].Write(writer);
+
+ writer.Align(sizeof(uint));
+ }
+
+ private void WriteSectionMapStream(IBinaryStreamWriter writer)
+ {
+ var maps = SectionMaps;
+
+ // Count and LogCount.
+ writer.WriteUInt16((ushort) maps.Count);
+ writer.WriteUInt16((ushort) maps.Count);
+
+ // Entries.
+ for (int i = 0; i < maps.Count; i++)
+ maps[i].Write(writer);
+
+ writer.Align(sizeof(uint));
+ }
+
+ private void WriteSourceInfoStream(IBinaryStreamWriter writer)
+ {
+ var sourceFiles = SourceFiles;
+ int totalFileCount = sourceFiles.Sum(x => x.Count);
+
+ // Module and total file count (truncated to 16 bits)
+ writer.WriteUInt16((ushort) (sourceFiles.Count & 0xFFFF));
+ writer.WriteUInt16((ushort) (totalFileCount & 0xFFFF));
+
+ // Module indices. Unsure if this is correct, but this array does not seem to be really used by the ref impl.
+ for (ushort i = 0; i < sourceFiles.Count; i++)
+ writer.WriteUInt16(i);
+
+ // Source file counts.
+ for (int i = 0; i < sourceFiles.Count; i++)
+ writer.WriteUInt16((ushort) sourceFiles[i].Count);
+
+ // Build up string buffer and name offset table.
+ using var stringBuffer = new MemoryStream();
+ var stringWriter = new BinaryStreamWriter(stringBuffer);
+ var stringOffsets = new Dictionary();
+
+ for (int i = 0; i < sourceFiles.Count; i++)
+ {
+ var collection = sourceFiles[i];
+ for (int j = 0; j < collection.Count; j++)
+ {
+ // If not present already, append to string buffer.
+ var name = collection[j];
+ if (!stringOffsets.TryGetValue(name, out uint offset))
+ {
+ offset = (uint) stringWriter.Offset;
+ stringOffsets[name] = offset;
+ stringWriter.WriteBytes(name.GetBytesUnsafe());
+ stringWriter.WriteByte(0);
+ }
+
+ // Write name offset
+ writer.WriteUInt32(offset);
+ }
+ }
+
+ // Write string buffer.
+ writer.WriteBytes(stringBuffer.ToArray());
+
+ writer.Align(sizeof(uint));
+ }
+
+ private void WriteTypeServerMapStream(IBinaryStreamWriter writer)
+ {
+ TypeServerMapStream?.Write(writer);
+ writer.Align(sizeof(uint));
+ }
+
+ private void WriteOptionalDebugStream(IBinaryStreamWriter writer)
+ {
+ var extraIndices = ExtraStreamIndices;
+
+ for (int i = 0; i < extraIndices.Count; i++)
+ writer.WriteUInt16(extraIndices[i]);
+ }
+
+ private void WriteECStream(IBinaryStreamWriter writer) => ECStream?.Write(writer);
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs
new file mode 100644
index 000000000..fe76de860
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs
@@ -0,0 +1,15 @@
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Provides members defining all possible DBI stream format version numbers.
+///
+public enum DbiStreamVersion
+{
+#pragma warning disable CS1591
+ VC41 = 930803,
+ V50 = 19960307,
+ V60 = 19970606,
+ V70 = 19990903,
+ V110 = 20091201
+#pragma warning restore CS1591
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs
new file mode 100644
index 000000000..174ec2dab
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs
@@ -0,0 +1,207 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Represents a reference to a single module (object file) that was linked into a program.
+///
+public class ModuleDescriptor : IWritable
+{
+ ///
+ /// Gets or sets a description of the section within the final binary which contains code
+ /// and/or data from this module.
+ ///
+ public SectionContribution SectionContribution
+ {
+ get;
+ set;
+ } = new();
+
+ ///
+ /// Gets or sets the attributes assigned to this module descriptor.
+ ///
+ public ModuleDescriptorAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the index of the type server for this module.
+ ///
+ public ushort TypeServerIndex
+ {
+ get => (ushort) ((ushort) Attributes >> 8);
+ set => Attributes = (Attributes & ~ModuleDescriptorAttributes.TsmMask) | (ModuleDescriptorAttributes) (value << 8);
+ }
+
+ ///
+ /// Gets or sets the MSF stream index of the stream that the symbols of this module.
+ ///
+ public ushort SymbolStreamIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the size of the CodeView data within the module's symbol stream.
+ ///
+ public uint SymbolDataSize
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the size of the C11-style CodeView data within the module's symbol stream.
+ ///
+ public uint SymbolC11DataSize
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the size of the C13-style CodeView data within the module's symbol stream.
+ ///
+ public uint SymbolC13DataSize
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of source files that contributed to this module during the compilation.
+ ///
+ public ushort SourceFileCount
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the offset in the names buffer of the primary translation unit.
+ ///
+ ///
+ /// For most compilers this value is set to zero.
+ ///
+ public uint SourceFileNameIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the offset in the names buffer of the PDB file.
+ ///
+ ///
+ /// For most modules (except the special * LINKER * module) this value is set to zero.
+ ///
+ public uint PdbFilePathNameIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the name of the module.
+ ///
+ ///
+ /// This is often a full path to the object file that was passed into link.exe directly, or a string in the
+ /// form of Import:dll_name
+ ///
+ public Utf8String? ModuleName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the name of the object file name.
+ ///
+ ///
+ /// In the case this module is linked directly passed to link.exe , this is the same as .
+ /// If the module comes from an archive, this is the full path to that archive.
+ ///
+ public Utf8String? ObjectFileName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Parses a single module descriptor from the provided input stream.
+ ///
+ /// The input stream.
+ /// THe parsed module descriptor.
+ public static ModuleDescriptor FromReader(ref BinaryStreamReader reader)
+ {
+ var result = new ModuleDescriptor();
+
+ reader.ReadUInt32();
+ result.SectionContribution = SectionContribution.FromReader(ref reader);
+ result.Attributes = (ModuleDescriptorAttributes) reader.ReadUInt16();
+ result.SymbolStreamIndex = reader.ReadUInt16();
+ result.SymbolDataSize = reader.ReadUInt32();
+ result.SymbolC11DataSize = reader.ReadUInt32();
+ result.SymbolC13DataSize = reader.ReadUInt32();
+ result.SourceFileCount = reader.ReadUInt16();
+ reader.ReadUInt16();
+ reader.ReadUInt32();
+ result.SourceFileNameIndex = reader.ReadUInt32();
+ result.PdbFilePathNameIndex = reader.ReadUInt32();
+ result.ModuleName = reader.ReadUtf8String();
+ result.ObjectFileName = reader.ReadUtf8String();
+ reader.Align(4);
+
+ return result;
+ }
+
+ ///
+ public uint GetPhysicalSize()
+ {
+ return (sizeof(uint) // Unused1
+ + SectionContribution.GetPhysicalSize() // SectionContribution
+ + sizeof(ModuleDescriptorAttributes) // Attributes
+ + sizeof(ushort) // SymbolStreamIndex
+ + sizeof(uint) // SymbolDataSize
+ + sizeof(uint) // SymbolC11DataSize
+ + sizeof(uint) // SymbolC13DataSize
+ + sizeof(ushort) // SourceFileCount
+ + sizeof(ushort) // Padding
+ + sizeof(uint) // Unused2
+ + sizeof(uint) // SourceFileNameIndex
+ + sizeof(uint) // PdbFilePathNameIndex
+ + (uint) (ModuleName?.ByteCount ?? 0) + 1 // ModuleName
+ + (uint) (ObjectFileName?.ByteCount ?? 0) + 1 // ObjectFileName
+ ).Align(4);
+ }
+
+ ///
+ public void Write(IBinaryStreamWriter writer)
+ {
+ writer.WriteUInt32(0);
+ SectionContribution.Write(writer);
+ writer.WriteUInt16((ushort) Attributes);
+ writer.WriteUInt16(SymbolStreamIndex);
+ writer.WriteUInt32(SymbolDataSize);
+ writer.WriteUInt32(SymbolC11DataSize);
+ writer.WriteUInt32(SymbolC13DataSize);
+ writer.WriteUInt16(SourceFileCount);
+ writer.WriteUInt16(0);
+ writer.WriteUInt32(0);
+ writer.WriteUInt32(SourceFileNameIndex);
+ writer.WriteUInt32(PdbFilePathNameIndex);
+ if (ModuleName is not null)
+ writer.WriteBytes(ModuleName.GetBytesUnsafe());
+ writer.WriteByte(0);
+ if (ObjectFileName is not null)
+ writer.WriteBytes(ObjectFileName.GetBytesUnsafe());
+ writer.WriteByte(0);
+ writer.Align(4);
+ }
+
+ ///
+ public override string ToString() => ModuleName ?? ObjectFileName ?? "?";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs
new file mode 100644
index 000000000..5b6a018c0
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Defines all possible flags that can be assigned to a module descriptor.
+///
+[Flags]
+public enum ModuleDescriptorAttributes : ushort
+{
+ ///
+ /// Indicates the module has been written to since reading the PDB.
+ ///
+ Dirty = 1,
+
+ ///
+ /// Indicates the module contains Edit & Continue information.
+ ///
+ EC = 2,
+
+ ///
+ /// Provides a mask for the type server index that is stored within the flags.
+ ///
+ TsmMask = 0xFF00,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs
new file mode 100644
index 000000000..83daaae3e
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs
@@ -0,0 +1,128 @@
+using System.Text;
+using AsmResolver.IO;
+using AsmResolver.PE.File.Headers;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Describes the section in the final executable file that a particular object or module is stored at.
+///
+public class SectionContribution : IWritable
+{
+ ///
+ /// The total size in bytes of a single on the disk.
+ ///
+ public const int EntrySize =
+ sizeof(ushort) // Section
+ + sizeof(ushort) // Padding1
+ + sizeof(uint) // Offset
+ + sizeof(uint) // Size
+ + sizeof(uint) // Characteristics
+ + sizeof(ushort) // ModuleIndex
+ + sizeof(ushort) // Padding2
+ + sizeof(uint) // DataCrc
+ + sizeof(uint) // RelocCrc
+ ;
+
+ ///
+ /// Gets or sets the index of the section.
+ ///
+ public ushort Section
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the offset within the section that this contribution starts at.
+ ///
+ public uint Offset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the size of the section contribution.
+ ///
+ public uint Size
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the section flags and permissions associated to this section contribution.
+ ///
+ public SectionFlags Characteristics
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the index of the module.
+ ///
+ public ushort ModuleIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets a cyclic redundancy code that can be used to verify the data section of this contribution.
+ ///
+ public uint DataCrc
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets a cyclic redundancy code that can be used to verify the relocation section of this contribution.
+ ///
+ public uint RelocCrc
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Parses a single section contribution from the provided input stream.
+ ///
+ /// The input stream.
+ /// The parsed section contribution.
+ public static SectionContribution FromReader(ref BinaryStreamReader reader)
+ {
+ var result = new SectionContribution();
+
+ result.Section = reader.ReadUInt16();
+ reader.ReadUInt16();
+ result.Offset = reader.ReadUInt32();
+ result.Size = reader.ReadUInt32();
+ result.Characteristics = (SectionFlags) reader.ReadUInt32();
+ result.ModuleIndex = reader.ReadUInt16();
+ reader.ReadUInt16();
+ result.DataCrc = reader.ReadUInt32();
+ result.RelocCrc = reader.ReadUInt32();
+
+ return result;
+ }
+
+ ///
+ public uint GetPhysicalSize() => EntrySize;
+
+ ///
+ public void Write(IBinaryStreamWriter writer)
+ {
+ writer.WriteUInt16(Section);
+ writer.WriteUInt16(0);
+ writer.WriteUInt32(Offset);
+ writer.WriteUInt32(Size);
+ writer.WriteUInt32((uint) Characteristics);
+ writer.WriteUInt16(ModuleIndex);
+ writer.WriteUInt16(0);
+ writer.WriteUInt32(DataCrc);
+ writer.WriteUInt32(RelocCrc);
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs
new file mode 100644
index 000000000..48daaf3a4
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs
@@ -0,0 +1,17 @@
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Provides members defining all valid versions of the Section Contribution sub stream's file format.
+///
+public enum SectionContributionStreamVersion : uint
+{
+ ///
+ /// Indicates version 6.0 is used.
+ ///
+ Ver60 = 0xeffe0000 + 19970605,
+
+ ///
+ /// Indicates version 2.0 is used.
+ ///
+ V2 = 0xeffe0000 + 20140516
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs
new file mode 100644
index 000000000..bf43a7e56
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs
@@ -0,0 +1,131 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Represents a single entry in the Section Map sub stream of the DBI stream.
+///
+public class SectionMap : IWritable
+{
+ ///
+ /// The total size in bytes of a single on the disk.
+ ///
+ public const int EntrySize =
+ sizeof(ushort) // Attributes
+ + sizeof(ushort) // Ovl
+ + sizeof(ushort) // Group
+ + sizeof(ushort) // Frame
+ + sizeof(ushort) // SectionName
+ + sizeof(ushort) // ClassName
+ + sizeof(uint) // Offset
+ + sizeof(uint) // SectionLength
+ ;
+
+ ///
+ /// Gets or sets the attributes assigned to this section map.
+ ///
+ public SectionMapAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the logical overlay number of this section map.
+ ///
+ public ushort LogicalOverlayNumber
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the group index into the descriptor array.
+ ///
+ public ushort Group
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the frame index.
+ ///
+ public ushort Frame
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the byte offset of the segment or group name in string table, or 0xFFFF if no name was assigned.
+ ///
+ public ushort SectionName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the byte offset of the class in the string table, or 0xFFFF if no name was assigned..
+ ///
+ public ushort ClassName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the byte offset of the logical segment within physical segment. If group is set in flags, this is the offset of the group.
+ ///
+ public uint Offset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of bytes that the segment or group consists of.
+ ///
+ public uint SectionLength
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Parses a single section map from the provided input stream.
+ ///
+ /// The input stream.
+ /// The parsed section map.
+ public static SectionMap FromReader(ref BinaryStreamReader reader)
+ {
+ return new SectionMap
+ {
+ Attributes = (SectionMapAttributes) reader.ReadUInt16(),
+ LogicalOverlayNumber = reader.ReadUInt16(),
+ Group = reader.ReadUInt16(),
+ Frame = reader.ReadUInt16(),
+ SectionName = reader.ReadUInt16(),
+ ClassName = reader.ReadUInt16(),
+ Offset = reader.ReadUInt32(),
+ SectionLength = reader.ReadUInt32()
+ };
+ }
+
+ ///
+ public uint GetPhysicalSize() => EntrySize;
+
+ ///
+ public void Write(IBinaryStreamWriter writer)
+ {
+ writer.WriteUInt16((ushort) Attributes);
+ writer.WriteUInt16(LogicalOverlayNumber);
+ writer.WriteUInt16(Group);
+ writer.WriteUInt16(Frame);
+ writer.WriteUInt16(SectionName);
+ writer.WriteUInt16(ClassName);
+ writer.WriteUInt32(Offset);
+ writer.WriteUInt32(SectionLength);
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs
new file mode 100644
index 000000000..7f6449772
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs
@@ -0,0 +1,42 @@
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Provides members describing all possible attributes that can be assigned to a single section map entry.
+///
+public enum SectionMapAttributes : ushort
+{
+ ///
+ /// Indicates the segment is readable.
+ ///
+ Read = 1 << 0,
+
+ ///
+ /// Indicates the segment is writable.
+ ///
+ Write = 1 << 1,
+
+ ///
+ /// Indicates the segment is executable.
+ ///
+ Execute = 1 << 2,
+
+ ///
+ /// Indicates the descriptor describes a 32-bit linear address.
+ ///
+ AddressIs32Bit = 1 << 3,
+
+ ///
+ /// Indicates the frame represents a selector.
+ ///
+ IsSelector = 1 << 8,
+
+ ///
+ /// Indicates the frame represents an absolute address.
+ ///
+ IsAbsoluteAddress = 1 << 9,
+
+ ///
+ /// Indicates the descriptor represents a group.
+ ///
+ IsGroup = 1 << 10
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs
new file mode 100644
index 000000000..962f02fc7
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using AsmResolver.IO;
+using AsmResolver.PE.File.Headers;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Implements a DBI stream that pulls its data from an input stream.
+///
+public class SerializedDbiStream : DbiStream
+{
+ private readonly BinaryStreamReader _moduleInfoReader;
+ private readonly BinaryStreamReader _sectionContributionReader;
+ private readonly BinaryStreamReader _sectionMapReader;
+ private readonly BinaryStreamReader _sourceInfoReader;
+ private readonly BinaryStreamReader _typeServerMapReader;
+ private readonly BinaryStreamReader _optionalDebugHeaderReader;
+ private readonly BinaryStreamReader _ecReader;
+
+ ///
+ /// Parses a DBI stream from an input stream reader.
+ ///
+ /// The input stream.
+ public SerializedDbiStream(BinaryStreamReader reader)
+ {
+ VersionSignature = reader.ReadInt32();
+ VersionHeader = (DbiStreamVersion) reader.ReadUInt32();
+ Age = reader.ReadUInt32();
+ GlobalStreamIndex = reader.ReadUInt16();
+ BuildNumber = reader.ReadUInt16();
+ if (!IsNewVersionFormat)
+ throw new NotSupportedException("The DBI stream uses the legacy file format, which is not supported.");
+
+ PublicStreamIndex = reader.ReadUInt16();
+ PdbDllVersion = reader.ReadUInt16();
+ SymbolRecordStreamIndex = reader.ReadUInt16();
+ PdbDllRbld = reader.ReadUInt16();
+
+ uint moduleInfoSize = reader.ReadUInt32();
+ uint sectionContributionSize = reader.ReadUInt32();
+ uint sectionMapSize = reader.ReadUInt32();
+ uint sourceInfoSize = reader.ReadUInt32();
+ uint typeServerMapSize = reader.ReadUInt32();
+
+ MfcTypeServerIndex = reader.ReadUInt32();
+
+ uint optionalDebugHeaderSize = reader.ReadUInt32();
+ uint ecSize = reader.ReadUInt32();
+
+ Attributes = (DbiAttributes) reader.ReadUInt16();
+ Machine = (MachineType) reader.ReadUInt16();
+
+ _ = reader.ReadUInt32();
+
+ _moduleInfoReader = reader.ForkRelative(reader.RelativeOffset, moduleInfoSize);
+ reader.Offset += moduleInfoSize;
+ _sectionContributionReader = reader.ForkRelative(reader.RelativeOffset, sectionContributionSize);
+ reader.Offset += sectionContributionSize;
+ _sectionMapReader = reader.ForkRelative(reader.RelativeOffset, sectionMapSize);
+ reader.Offset += sectionMapSize;
+ _sourceInfoReader = reader.ForkRelative(reader.RelativeOffset, sourceInfoSize);
+ reader.Offset += sourceInfoSize;
+ _typeServerMapReader = reader.ForkRelative(reader.RelativeOffset, typeServerMapSize);
+ reader.Offset += typeServerMapSize;
+ _ecReader = reader.ForkRelative(reader.RelativeOffset, ecSize);
+ reader.Offset += ecSize;
+ _optionalDebugHeaderReader = reader.ForkRelative(reader.RelativeOffset, optionalDebugHeaderSize);
+ reader.Offset += optionalDebugHeaderSize;
+ }
+
+ ///
+ protected override IList GetModules()
+ {
+ var result = new List();
+
+ var reader = _moduleInfoReader.Fork();
+ while (reader.CanRead(1))
+ result.Add(ModuleDescriptor.FromReader(ref reader));
+
+ return result;
+ }
+
+ ///
+ protected override IList GetSectionContributions()
+ {
+ var result = new List();
+
+ var reader = _sectionContributionReader.Fork();
+ var version = (SectionContributionStreamVersion) reader.ReadUInt32();
+
+ while (reader.CanRead(1))
+ {
+ result.Add(SectionContribution.FromReader(ref reader));
+ if (version == SectionContributionStreamVersion.V2)
+ reader.ReadUInt32();
+ }
+
+ return result;
+ }
+
+ ///
+ protected override IList GetSectionMaps()
+ {
+ var reader = _sectionMapReader.Fork();
+
+ ushort count = reader.ReadUInt16();
+ ushort logCount = reader.ReadUInt16();
+
+ var result = new List(count);
+ for (int i = 0; i < count; i++)
+ result.Add(SectionMap.FromReader(ref reader));
+
+ return result;
+ }
+
+ ///
+ protected override ISegment? GetTypeServerMapStream()
+ {
+ var reader = _typeServerMapReader.Fork();
+ return reader.Length != 0
+ ? reader.ReadSegment(reader.Length)
+ : null;
+ }
+
+ ///
+ protected override ISegment? GetECStream()
+ {
+ var reader = _ecReader.Fork();
+ return reader.Length != 0
+ ? reader.ReadSegment(reader.Length)
+ : null;
+ }
+
+ ///
+ protected override IList GetSourceFiles()
+ {
+ var reader = _sourceInfoReader.Fork();
+
+ ushort moduleCount = reader.ReadUInt16();
+ ushort sourceFileCount = reader.ReadUInt16();
+
+ // Read module indices.
+ ushort[] moduleIndices = new ushort[moduleCount];
+ for (int i = 0; i < moduleCount; i++)
+ moduleIndices[i] = reader.ReadUInt16();
+
+ // Read module source file counts.
+ int actualFileCount = 0;
+ ushort[] moduleFileCounts = new ushort[moduleCount];
+ for (int i = 0; i < moduleCount; i++)
+ {
+ ushort count = reader.ReadUInt16();
+ moduleFileCounts[i] = count;
+ actualFileCount += count;
+ }
+
+ // Scope on the name buffer.
+ var stringReaderBuffer = reader.ForkRelative((uint) (reader.RelativeOffset + actualFileCount * sizeof(uint)));
+
+ // Construct source file lists.
+ var result = new List(moduleCount);
+ for (int i = 0; i < moduleCount; i++)
+ {
+ var files = new SourceFileCollection(moduleIndices[i]);
+ ushort fileCount = moduleFileCounts[i];
+
+ // Read all file paths for this module.
+ for (int j = 0; j < fileCount; j++)
+ {
+ uint nameOffset = reader.ReadUInt32();
+ var nameReader = stringReaderBuffer.ForkRelative(nameOffset);
+ files.Add(nameReader.ReadUtf8String());
+ }
+
+ result.Add(files);
+ }
+
+ return result;
+ }
+
+ ///
+ protected override IList GetExtraStreamIndices()
+ {
+ var reader = _optionalDebugHeaderReader.Fork();
+
+ var result = new List((int) (reader.Length / sizeof(ushort)));
+
+ while (reader.CanRead(sizeof(ushort)))
+ result.Add(reader.ReadUInt16());
+
+ return result;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs
new file mode 100644
index 000000000..b7ef673e6
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Dbi;
+
+///
+/// Represents a collection of file paths used as input source code for a single module.
+///
+public class SourceFileCollection : List
+{
+ ///
+ /// Creates a new empty source file collection.
+ ///
+ public SourceFileCollection()
+ {
+ }
+
+ ///
+ /// Creates a new empty source file collection.
+ ///
+ /// The original module index for which this collection was compiled.
+ public SourceFileCollection(uint originalModuleIndex)
+ {
+ OriginalModuleIndex = originalModuleIndex;
+ }
+
+ ///
+ /// Gets the original module index for which this collection was compiled (if available).
+ ///
+ ///
+ /// The exact purpose of this number is unclear, as this number cannot be reliably used as an index within the
+ /// DBI stream's module list. Use the index of this list within instead.
+ ///
+ public uint OriginalModuleIndex
+ {
+ get;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs
new file mode 100644
index 000000000..eae02174c
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Info;
+
+///
+/// Represents the PDB Info Stream (also known as the PDB stream)
+///
+public class InfoStream : SegmentBase
+{
+ ///
+ /// Gets the default fixed MSF stream index for the PDB Info stream.
+ ///
+ public const int StreamIndex = 1;
+
+ private const int HeaderSize =
+ sizeof(InfoStreamVersion) // Version
+ + sizeof(uint) // Signature
+ + sizeof(uint) // Aage
+ + 16 //UniqueId
+ + sizeof(uint) // NameBufferSize
+ ;
+
+ private IDictionary? _streamIndices;
+ private IList? _features;
+
+ ///
+ /// Gets or sets the version of the file format of the PDB info stream.
+ ///
+ ///
+ /// Modern tooling only recognize the VC7.0 file format.
+ ///
+ public InfoStreamVersion Version
+ {
+ get;
+ set;
+ } = InfoStreamVersion.VC70;
+
+ ///
+ /// Gets or sets the 32-bit UNIX time-stamp of the PDB file.
+ ///
+ public uint Signature
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of times the PDB file has been written.
+ ///
+ public uint Age
+ {
+ get;
+ set;
+ } = 1;
+
+ ///
+ /// Gets or sets the unique identifier assigned to the PDB file.
+ ///
+ public Guid UniqueId
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets a mapping from stream names to their respective stream index within the underlying MSF file.
+ ///
+ public IDictionary StreamIndices
+ {
+ get
+ {
+ if (_streamIndices is null)
+ Interlocked.CompareExchange(ref _streamIndices, GetStreamIndices(), null);
+ return _streamIndices;
+ }
+ }
+
+ ///
+ /// Gets a list of characteristics that this PDB has.
+ ///
+ public IList Features
+ {
+ get
+ {
+ if (_features is null)
+ Interlocked.CompareExchange(ref _features, GetFeatures(), null);
+ return _features;
+ }
+ }
+
+ ///
+ /// Reads a single PDB info stream from the provided input stream.
+ ///
+ /// The input stream.
+ /// The parsed info stream.
+ public static InfoStream FromReader(BinaryStreamReader reader) => new SerializedInfoStream(reader);
+
+ ///
+ /// Obtains the stream name to index mapping of the PDB file.
+ ///
+ /// The mapping.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IDictionary GetStreamIndices() => new Dictionary();
+
+ ///
+ /// Obtains the features of the PDB file.
+ ///
+ /// The features.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetFeatures() => new List
+ {
+ PdbFeature.VC140
+ };
+
+ ///
+ public override uint GetPhysicalSize()
+ {
+ uint totalSize = HeaderSize;
+
+ // Name buffer
+ foreach (var entry in StreamIndices)
+ totalSize += (uint) entry.Key.ByteCount + 1u;
+
+ // Stream indices hash table.
+ totalSize += StreamIndices.GetPdbHashTableSize(ComputeStringHash);
+
+ // Last NI
+ totalSize += sizeof(uint);
+
+ // Feature codes.
+ totalSize += (uint) Features.Count * sizeof(PdbFeature);
+
+ return totalSize;
+ }
+
+ ///
+ public override void Write(IBinaryStreamWriter writer)
+ {
+ // Write basic info stream header.
+ writer.WriteUInt32((uint) Version);
+ writer.WriteUInt32(Signature);
+ writer.WriteUInt32(Age);
+ writer.WriteBytes(UniqueId.ToByteArray());
+
+ // Construct name buffer, keeping track of the offsets of every name.
+ using var nameBuffer = new MemoryStream();
+ var nameWriter = new BinaryStreamWriter(nameBuffer);
+
+ var stringOffsets = new Dictionary();
+ foreach (var entry in StreamIndices)
+ {
+ uint offset = (uint) nameWriter.Offset;
+ nameWriter.WriteBytes(entry.Key.GetBytesUnsafe());
+ nameWriter.WriteByte(0);
+ stringOffsets.Add(entry.Key, offset);
+ }
+
+ writer.WriteUInt32((uint) nameBuffer.Length);
+ writer.WriteBytes(nameBuffer.ToArray());
+
+ // Write the hash table.
+ StreamIndices.WriteAsPdbHashTable(writer,
+ ComputeStringHash,
+ (key, value) => (stringOffsets[key], (uint) value));
+
+ // last NI, safe to put always zero.
+ writer.WriteUInt32(0);
+
+ // Write feature codes.
+ var features = Features;
+ for (int i = 0; i < features.Count; i++)
+ writer.WriteUInt32((uint) features[i]);
+ }
+
+ private static uint ComputeStringHash(Utf8String str)
+ {
+ // Note: The hash of a single entry is **deliberately** truncated to a 16 bit number. This is because
+ // the reference implementation of the name table returns a number of type HASH, which is a typedef
+ // for "unsigned short". If we don't do this, this will result in wrong buckets being filled in the
+ // hash table, and thus the serialization would fail. See NMTNI::hash() in Microsoft/microsoft-pdb.
+
+ return (ushort) PdbHash.ComputeV1(str);
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs
new file mode 100644
index 000000000..d377f5ba1
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs
@@ -0,0 +1,20 @@
+namespace AsmResolver.Symbols.Pdb.Metadata.Info;
+
+///
+/// Provides members defining all possible stream file format versions that PDB defines.
+///
+public enum InfoStreamVersion
+{
+#pragma warning disable CS1591
+ VC2 = 19941610,
+ VC4 = 19950623,
+ VC41 = 19950814,
+ VC50 = 19960307,
+ VC98 = 19970604,
+ VC70Dep = 19990604,
+ VC70 = 20000404,
+ VC80 = 20030901,
+ VC110 = 20091201,
+ VC140 = 20140508,
+#pragma warning restore CS1591
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs
new file mode 100644
index 000000000..16e5d3a66
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs
@@ -0,0 +1,28 @@
+namespace AsmResolver.Symbols.Pdb.Metadata.Info;
+
+///
+/// Provides members defining all possible features that a PDB can have.
+///
+public enum PdbFeature : uint
+{
+ ///
+ /// Indicates no other feature flags are present, and that an IPI stream is present.
+ ///
+ VC110 = 20091201,
+
+ ///
+ /// Indicates that other feature flags may be present, and that an IPI stream is present.
+ ///
+ VC140 = 20140508,
+
+ ///
+ /// Indicates types can be duplicated in the TPI stream.
+ ///
+ NoTypeMerge = 0x4D544F4E,
+
+ ///
+ /// Indicates the program was linked with /DEBUG:FASTLINK, and all type information is contained in the original
+ /// object files instead of TPI and IPI streams.
+ ///
+ MinimalDebugInfo = 0x494E494D,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs
new file mode 100644
index 000000000..f448ce67f
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Info;
+
+///
+/// Implements an PDB info stream that pulls its data from an input stream.
+///
+public class SerializedInfoStream : InfoStream
+{
+ private readonly BinaryStreamReader _reader;
+ private ulong _featureOffset;
+
+ ///
+ /// Parses a PDB info stream from an input stream reader.
+ ///
+ /// The input stream.
+ public SerializedInfoStream(BinaryStreamReader reader)
+ {
+ Version = (InfoStreamVersion) reader.ReadUInt32();
+ Signature = reader.ReadUInt32();
+ Age = reader.ReadUInt32();
+
+ byte[] guidBytes = new byte[16];
+ reader.ReadBytes(guidBytes, 0, guidBytes.Length);
+
+ UniqueId = new Guid(guidBytes);
+
+ _reader = reader;
+ }
+
+ ///
+ protected override IDictionary GetStreamIndices()
+ {
+ var reader = _reader.Fork();
+ uint length = reader.ReadUInt32();
+
+ var stringsReader = reader.ForkRelative(reader.RelativeOffset, length);
+ var hashTableReader = reader.ForkRelative(reader.RelativeOffset + length);
+
+ var result = PdbHashTable.FromReader(ref hashTableReader, (key, value) =>
+ {
+ var stringReader = stringsReader.ForkRelative(key);
+ var keyString = stringReader.ReadUtf8String();
+ return (keyString, (int) value);
+ });
+
+ hashTableReader.ReadUInt32(); // lastNi (unused).
+
+ _featureOffset = hashTableReader.Offset;
+ return result;
+ }
+
+ ///
+ protected override IList GetFeatures()
+ {
+ // We need to read the stream name->index mapping to be able to read the features list of the PDB.
+ _ = StreamIndices;
+
+ var result = new List();
+
+ var reader = _reader.ForkAbsolute(_featureOffset);
+ while (reader.CanRead(sizeof(uint)))
+ result.Add((PdbFeature) reader.ReadUInt32());
+
+ return result;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs
new file mode 100644
index 000000000..ed5a6a03b
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs
@@ -0,0 +1,49 @@
+namespace AsmResolver.Symbols.Pdb.Metadata;
+
+///
+/// Provides methods for computing hash codes for a PDB hash table.
+///
+public static class PdbHash
+{
+ ///
+ /// Computes the V1 hash code for a UTF-8 string.
+ ///
+ /// The string to compute the hash for.
+ /// The hash code.
+ ///
+ /// See PDB/include/misc.h for reference implementation.
+ ///
+ public static unsafe uint ComputeV1(Utf8String value)
+ {
+ uint result = 0;
+
+ uint count = (uint) value.ByteCount;
+
+ fixed (byte* ptr = value.GetBytesUnsafe())
+ {
+ byte* p = ptr;
+
+ while (count >= 4)
+ {
+ result ^= *(uint*) p;
+ count -= 4;
+ p += 4;
+ }
+
+ if (count >= 2)
+ {
+ result ^= *(ushort*) p;
+ count -= 2;
+ p += 2;
+ }
+
+ if (count == 1)
+ result ^= *p;
+ }
+
+ result |= 0x20202020;
+ result ^= result >> 11;
+
+ return result ^ (result >> 16);
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs
new file mode 100644
index 000000000..fb58f25be
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs
@@ -0,0 +1,212 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Metadata;
+
+///
+/// Provides methods for serializing and deserializing dictionaries as PDB hash tables.
+///
+public static class PdbHashTable
+{
+ // Reference implementation from PDB/include/map.h
+ // Specifically, Map::load, Map::find and Map::save.
+
+ ///
+ /// Reads a single PDB hash table from the input stream and converts it into a dictionary.
+ ///
+ /// The input stream to read from.
+ /// A function that maps the raw key-value pairs into high level constructs.
+ /// The type of keys in the final dictionary.
+ /// The type of values in the final dictionary.
+ /// The reconstructed dictionary.
+ public static Dictionary FromReader(
+ ref BinaryStreamReader reader,
+ Func mapper)
+ where TKey : notnull
+ {
+ uint count = reader.ReadUInt32();
+ reader.ReadUInt32(); // Capacity
+
+ uint presentWordCount = reader.ReadUInt32();
+ reader.RelativeOffset += presentWordCount * sizeof(uint);
+
+ uint deletedWordCount = reader.ReadUInt32();
+ reader.RelativeOffset += deletedWordCount * sizeof(uint);
+
+ var result = new Dictionary((int) count);
+ for (int i = 0; i < count; i++)
+ {
+ (uint rawKey, uint rawValue) = (reader.ReadUInt32(), reader.ReadUInt32());
+ var (key, value) = mapper(rawKey, rawValue);
+ result.Add(key, value);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Computes the number of bytes required to store the provided dictionary as a PDB hash table.
+ ///
+ /// The dictionary to serialize.
+ /// A function that computes the hash code for a single key within the dictionary.
+ /// The type of keys in the input dictionary.
+ /// The type of values in the input dictionary.
+ /// The number of bytes required.
+ public static uint GetPdbHashTableSize(
+ this IDictionary dictionary,
+ Func hasher)
+ where TKey : notnull
+ {
+ var info = dictionary.ToPdbHashTable(hasher, null);
+
+ return sizeof(uint) // Count
+ + sizeof(uint) // Capacity
+ + sizeof(uint) // Present bitvector word count
+ + info.PresentWordCount * sizeof(uint) // Present bitvector words
+ + sizeof(uint) // Deleted bitvector word count (== 0)
+ + (sizeof(uint) + sizeof(uint)) * (uint) dictionary.Count
+ ;
+ }
+
+ ///
+ /// Serializes a dictionary to a PDB hash table to an output stream.
+ ///
+ /// The dictionary to serialize.
+ /// The output stream to write to.
+ /// A function that computes the hash code for a single key within the dictionary.
+ /// A function that maps every key-value pair to raw key-value uint32 pairs.
+ /// The type of keys in the input dictionary.
+ /// The type of values in the input dictionary.
+ public static void WriteAsPdbHashTable(
+ this IDictionary dictionary,
+ IBinaryStreamWriter writer,
+ Func hasher,
+ Func mapper)
+ where TKey : notnull
+ {
+ var hashTable = dictionary.ToPdbHashTable(hasher, mapper);
+
+ // Write count and capacity.
+ writer.WriteInt32(dictionary.Count);
+ writer.WriteUInt32(hashTable.Capacity);
+
+ // Determine which words in the present bitvector to write.
+ uint wordCount = (hashTable.Capacity + sizeof(uint) - 1) / sizeof(uint);
+ uint[] words = new uint[wordCount];
+ hashTable.Present.CopyTo(words, 0);
+ while (wordCount > 0 && words[wordCount - 1] == 0)
+ wordCount--;
+
+ // Write the present bitvector.
+ writer.WriteUInt32(wordCount);
+ for (int i = 0; i < wordCount; i++)
+ writer.WriteUInt32(words[i]);
+
+ // Write deleted bitvector. We just always do 0 (i.e. no deleted buckets).
+ writer.WriteUInt32(0);
+
+ // Write all buckets.
+ for (int i = 0; i < hashTable.Keys!.Length; i++)
+ {
+ if (hashTable.Present.Get(i))
+ {
+ writer.WriteUInt32(hashTable.Keys![i]);
+ writer.WriteUInt32(hashTable.Values![i]);
+ }
+ }
+ }
+
+ private static HashTableInfo ToPdbHashTable(
+ this IDictionary dictionary,
+ Func hasher,
+ Func? mapper)
+ where TKey : notnull
+ {
+ uint capacity = ComputeRequiredCapacity(dictionary.Count);
+
+ // Avoid allocating buckets if we actually don't need to (e.g. if we're simply measuring the total size).
+ uint[]? keys;
+ uint[]? values;
+
+ if (mapper is null)
+ {
+ keys = null;
+ values = null;
+ }
+ else
+ {
+ keys = new uint[capacity];
+ values = new uint[capacity];
+ }
+
+ var present = new BitArray((int) capacity, false);
+
+ // Fill in buckets.
+ foreach (var item in dictionary)
+ {
+ // Find empty bucket to place key-value pair in.
+ uint hash = hasher(item.Key);
+ uint index = hash % capacity;
+ while (present.Get((int) index))
+ index = (index + 1) % capacity;
+
+ // Mark bucket as used.
+ present.Set((int) index, true);
+
+ // Store key-value pair.
+ if (mapper is not null)
+ {
+ (uint key, uint value) = mapper(item.Key, item.Value);
+ keys![index] = key;
+ values![index] = value;
+ }
+ }
+
+ // Determine final word count in present bit vector.
+ uint wordCount = (capacity + sizeof(uint) - 1) / sizeof(uint);
+ uint[] words = new uint[wordCount];
+ present.CopyTo(words, 0);
+ while (wordCount > 0 && words[wordCount - 1] == 0)
+ wordCount--;
+
+ return new HashTableInfo(capacity, keys, values, present, wordCount);
+ }
+
+ private static uint ComputeRequiredCapacity(int totalItemCount)
+ {
+ // "Simulate" adding all items to the hash table, effectively calculating the capacity of the map.
+ // TODO: This can probably be calculated with a single formula instead.
+
+ uint capacity = 1;
+ for (int i = 0; i <= totalItemCount; i++)
+ {
+ // Reference implementation allows only 67% of the capacity to be used.
+ uint maxLoad = capacity * 2 / 3 + 1;
+ if (i >= maxLoad)
+ capacity = 2 * maxLoad;
+ }
+
+ return capacity;
+ }
+
+ private readonly struct HashTableInfo
+ {
+ public readonly uint Capacity;
+ public readonly uint[]? Keys;
+ public readonly uint[]? Values;
+ public readonly BitArray Present;
+ public readonly uint PresentWordCount;
+
+ public HashTableInfo(uint capacity, uint[]? keys, uint[]? values, BitArray present, uint presentWordCount)
+ {
+ Capacity = capacity;
+ Keys = keys;
+ Values = values;
+ Present = present;
+ PresentWordCount = presentWordCount;
+ }
+ }
+
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs
new file mode 100644
index 000000000..3a4c144c7
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Tpi;
+
+///
+/// Provides a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedTpiStream : TpiStream
+{
+ private readonly BinaryStreamReader _recordsReader;
+ private List<(uint Offset, uint Length)>? _recordOffsets;
+
+ ///
+ /// Reads a TPI stream from the provided input stream.
+ ///
+ /// The input stream to read from.
+ public SerializedTpiStream(BinaryStreamReader reader)
+ {
+ Version = (TpiStreamVersion) reader.ReadUInt32();
+ if (Version != TpiStreamVersion.V80)
+ throw new NotSupportedException($"Unsupported TPI file format version {Version}.");
+
+ uint headerSize = reader.ReadUInt32();
+ if (headerSize != TpiStreamHeaderSize)
+ throw new NotSupportedException("Invalid TPI header size.");
+
+ TypeIndexBegin = reader.ReadUInt32();
+ TypeIndexEnd = reader.ReadUInt32();
+ TypeRecordsByteCount = reader.ReadUInt32();
+ HashStreamIndex = reader.ReadUInt16();
+ HashAuxStreamIndex = reader.ReadUInt16();
+ HashKeySize = reader.ReadUInt32();
+ HashBucketCount = reader.ReadUInt32();
+ HashValueBufferOffset = reader.ReadUInt32();
+ HashValueBufferLength = reader.ReadUInt32();
+ IndexOffsetBufferOffset = reader.ReadUInt32();
+ IndexOffsetBufferLength = reader.ReadUInt32();
+ HashAdjBufferOffset = reader.ReadUInt32();
+ HashAdjBufferLength = reader.ReadUInt32();
+
+ _recordsReader = reader.ForkRelative(reader.RelativeOffset, TypeRecordsByteCount);
+ }
+
+ [MemberNotNull(nameof(_recordOffsets))]
+ private void EnsureRecordOffsetMappingInitialized()
+ {
+ if (_recordOffsets is null)
+ Interlocked.CompareExchange(ref _recordOffsets, GetRecordOffsets(), null);
+ }
+
+ private List<(uint Offset, uint Length)> GetRecordOffsets()
+ {
+ int count = (int) (TypeIndexEnd - TypeIndexBegin);
+ var result = new List<(uint Offset, uint Length)>(count);
+
+ var reader = _recordsReader.Fork();
+ while (reader.CanRead(sizeof(ushort) * 2))
+ {
+ uint offset = reader.RelativeOffset;
+ ushort length = reader.ReadUInt16();
+ result.Add((offset, length));
+ reader.Offset += length;
+ }
+
+ return result;
+ }
+
+ ///
+ public override bool TryGetLeafRecordReader(uint typeIndex, out BinaryStreamReader reader)
+ {
+ EnsureRecordOffsetMappingInitialized();
+
+ typeIndex -= TypeIndexBegin;
+ if (typeIndex >= _recordOffsets.Count)
+ {
+ reader = default;
+ return false;
+ }
+
+ (uint offset, uint length) = _recordOffsets[(int) typeIndex];
+ reader = _recordsReader.ForkRelative(offset, length + sizeof(ushort));
+ return true;
+ }
+
+ ///
+ protected override void WriteTypeRecords(IBinaryStreamWriter writer)
+ {
+ _recordsReader.Fork().WriteToOutput(writer);
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs
new file mode 100644
index 000000000..f3a5440e3
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs
@@ -0,0 +1,233 @@
+using System;
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Metadata.Tpi;
+
+///
+/// Represents the Type Information (TPI) stream in a PDB file.
+///
+public abstract class TpiStream : SegmentBase
+{
+ ///
+ /// Gets the default fixed MSF stream index for the TPI stream.
+ ///
+ public const int StreamIndex = 2;
+
+ internal const uint TpiStreamHeaderSize =
+ sizeof(TpiStreamVersion) // Version
+ + sizeof(uint) // HeaderSize
+ + sizeof(uint) // TypeIndexBegin
+ + sizeof(uint) // TypeIndexEnd
+ + sizeof(uint) // TypeRecordBytes
+ + sizeof(ushort) // HashStreamIndex
+ + sizeof(ushort) // HashAuxStreamIndex
+ + sizeof(uint) // HashKeySize
+ + sizeof(uint) // NumHashBuckets
+ + sizeof(uint) // HashValueBufferOffset
+ + sizeof(uint) // HashValueBufferLength
+ + sizeof(uint) // IndexOffsetBufferOffset
+ + sizeof(uint) // IndexOffsetBufferLength
+ + sizeof(uint) // HashAdjBufferOffset
+ + sizeof(uint) // HashAdjBufferLength
+ ;
+
+ ///
+ /// Gets or sets the version of the file format that the TPI stream is using.
+ ///
+ public TpiStreamVersion Version
+ {
+ get;
+ set;
+ } = TpiStreamVersion.V80;
+
+ ///
+ /// Gets or sets the index of the first type record in the stream.
+ ///
+ public uint TypeIndexBegin
+ {
+ get;
+ set;
+ } = 0x1000;
+
+ ///
+ /// Gets or sets the index of the last type record in the stream.
+ ///
+ public uint TypeIndexEnd
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the amount of bytes the full type record data requires.
+ ///
+ public uint TypeRecordsByteCount
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the MSF stream index of the hash table for every record type in the stream (if available).
+ ///
+ ///
+ /// When this value is set to -1 (0xFFFF ), then there is no hash stream stored in the PDB file.
+ ///
+ public ushort HashStreamIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the MSF stream index of the auxiliary hash table for every record type in the stream.
+ ///
+ ///
+ /// When this value is set to -1 (0xFFFF ), then there is no hash stream stored in the PDB file.
+ /// The exact purpose of this stream is unknown, and usually this stream is not present.
+ ///
+ public ushort HashAuxStreamIndex
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of bytes that a single hash value in the type hash stream consists of.
+ ///
+ public uint HashKeySize
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of buckets used in the type record hash table.
+ ///
+ public uint HashBucketCount
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of hash values.
+ ///
+ public uint HashValueBufferOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of bytes within the TPI hash stream that the list of hash values consists of.
+ ///
+ public uint HashValueBufferLength
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of type record indices.
+ ///
+ public uint IndexOffsetBufferOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of bytes within the TPI hash stream that the list of type record indices consists of.
+ ///
+ public uint IndexOffsetBufferLength
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of hash-index pairs.
+ ///
+ public uint HashAdjBufferOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the number of bytes within the TPI hash stream that the list of hash-index pairs consists of.
+ ///
+ public uint HashAdjBufferLength
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Reads a TPI stream from the provided input stream.
+ ///
+ /// The input stream.
+ /// The TPI stream.
+ public static TpiStream FromReader(BinaryStreamReader reader) => new SerializedTpiStream(reader);
+
+ ///
+ /// Attempts to get a reader object that starts at the beginning of a leaf record for the provided type index.
+ ///
+ /// The type index to get the reader for.
+ /// The obtained reader object.
+ ///
+ /// true if the provided type index was valid and a reader object was constructed successfully,
+ /// false otherwise.
+ ///
+ public abstract bool TryGetLeafRecordReader(uint typeIndex, out BinaryStreamReader reader);
+
+ ///
+ /// Gets a reader object that starts at the beginning of a leaf record for the provided type index.
+ ///
+ /// The type index to get the reader for.
+ /// The obtained reader object.
+ /// Occurs when the provided type index was invalid.
+ public BinaryStreamReader GetLeafRecordReader(uint typeIndex)
+ {
+ if (!TryGetLeafRecordReader(typeIndex, out var reader))
+ throw new ArgumentException("Invalid type index.");
+
+ return reader;
+ }
+
+ ///
+ public override uint GetPhysicalSize() => TpiStreamHeaderSize + TypeRecordsByteCount;
+
+ ///
+ public override void Write(IBinaryStreamWriter writer)
+ {
+ WriteHeader(writer);
+ WriteTypeRecords(writer);
+ }
+
+ private void WriteHeader(IBinaryStreamWriter writer)
+ {
+ writer.WriteUInt32((uint) Version);
+ writer.WriteUInt32(TpiStreamHeaderSize);
+ writer.WriteUInt32(TypeIndexBegin);
+ writer.WriteUInt32(TypeIndexEnd);
+ writer.WriteUInt32(TypeRecordsByteCount);
+ writer.WriteUInt16(HashStreamIndex);
+ writer.WriteUInt16(HashAuxStreamIndex);
+ writer.WriteUInt32(HashKeySize);
+ writer.WriteUInt32(HashBucketCount);
+ writer.WriteUInt32(HashValueBufferOffset);
+ writer.WriteUInt32(HashValueBufferLength);
+ writer.WriteUInt32(IndexOffsetBufferOffset);
+ writer.WriteUInt32(IndexOffsetBufferLength);
+ writer.WriteUInt32(HashAdjBufferOffset);
+ writer.WriteUInt32(HashAdjBufferLength);
+ }
+
+ ///
+ /// Writes all type records stored in the TPI stream to the provided output stream.
+ ///
+ /// The output stream.
+ protected abstract void WriteTypeRecords(IBinaryStreamWriter writer);
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs
new file mode 100644
index 000000000..bf65e07ec
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs
@@ -0,0 +1,15 @@
+namespace AsmResolver.Symbols.Pdb.Metadata.Tpi;
+
+///
+/// Provides members defining all known file formats for the TPI stream.
+///
+public enum TpiStreamVersion : uint
+{
+#pragma warning disable CS1591
+ V40 = 19950410,
+ V41 = 19951122,
+ V50 = 19961031,
+ V70 = 19990903,
+ V80 = 20040203,
+#pragma warning restore CS1591
+}
diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs
new file mode 100644
index 000000000..0abd232a9
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using AsmResolver.IO;
+using AsmResolver.Symbols.Pdb.Leaves;
+using AsmResolver.Symbols.Pdb.Msf;
+using AsmResolver.Symbols.Pdb.Records;
+
+namespace AsmResolver.Symbols.Pdb;
+
+///
+/// Represents a single Program Debug Database (PDB) image.
+///
+public class PdbImage
+{
+ private IList? _symbols;
+ private ConcurrentDictionary _simpleTypes = new();
+
+ ///
+ /// Gets a collection of all symbols stored in the PDB image.
+ ///
+ public IList Symbols
+ {
+ get
+ {
+ if (_symbols is null)
+ Interlocked.CompareExchange(ref _symbols, GetSymbols(), null);
+ return _symbols;
+ }
+ }
+
+ ///
+ /// Reads a PDB image from the provided input file.
+ ///
+ /// The path to the PDB file.
+ /// The read PDB image.
+ public static PdbImage FromFile(string path) => FromFile(MsfFile.FromFile(path));
+
+ ///
+ /// Reads a PDB image from the provided input file.
+ ///
+ /// The input file.
+ /// The read PDB image.
+ public static PdbImage FromFile(IInputFile file) => FromFile(MsfFile.FromFile(file));
+
+ ///
+ /// Interprets a byte array as a PDB image.
+ ///
+ /// The data to interpret.
+ /// The read PDB image.
+ public static PdbImage FromBytes(byte[] data) => FromFile(MsfFile.FromBytes(data));
+
+ ///
+ /// Reads an PDB image from the provided input stream reader.
+ ///
+ /// The reader.
+ /// The read PDB image.
+ public static PdbImage FromReader(BinaryStreamReader reader) => FromFile(MsfFile.FromReader(reader));
+
+ ///
+ /// Loads a PDB image from the provided MSF file.
+ ///
+ /// The MSF file.
+ /// The read PDB image.
+ public static PdbImage FromFile(MsfFile file)
+ {
+ return FromFile(file, new PdbReaderParameters(ThrowErrorListener.Instance));
+ }
+
+ ///
+ /// Loads a PDB image from the provided MSF file.
+ ///
+ /// The MSF file.
+ /// The parameters to use while reading the PDB image.
+ /// The read PDB image.
+ public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParameters)
+ {
+ return new SerializedPdbImage(file, readerParameters);
+ }
+
+ ///
+ /// Attempts to obtain a type record from the TPI or IPI stream based on its type index.
+ ///
+ /// The type index.
+ /// The resolved type.
+ /// true if the type was found, false otherwise.
+ public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf)
+ {
+ typeIndex &= 0x7fffffff;
+ if (typeIndex is > 0 and < 0x1000)
+ {
+ leaf = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleTypeRecord(i));
+ return true;
+ }
+
+ leaf = null;
+ return false;
+ }
+
+ ///
+ /// Obtains a type record from the TPI or IPI stream based on its type index.
+ ///
+ /// The type index.
+ /// The resolved type.
+ /// Occurs when the type index is invalid.
+ public CodeViewLeaf GetLeafRecord(uint typeIndex)
+ {
+ if (!TryGetLeafRecord(typeIndex, out var type))
+ throw new ArgumentException("Invalid type index.");
+ return type;
+ }
+
+ ///
+ /// Obtains a collection of symbols stored in the symbol record stream of the PDB image.
+ ///
+ /// The symbols.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetSymbols() => new List();
+}
diff --git a/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs b/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs
new file mode 100644
index 000000000..9e2939315
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs
@@ -0,0 +1,34 @@
+namespace AsmResolver.Symbols.Pdb;
+
+///
+/// Provides a context in which a PDB image reader exists in. This includes the PDB image as well as reader parameters.
+///
+public class PdbReaderContext
+{
+ ///
+ /// Creates a new PDB reader context.
+ ///
+ /// The image for which the data is to be read.
+ /// The parameters used while reading the PDB image.
+ public PdbReaderContext(SerializedPdbImage parentImage, PdbReaderParameters parameters)
+ {
+ ParentImage = parentImage;
+ Parameters = parameters;
+ }
+
+ ///
+ /// Gets the image for which the data is read.
+ ///
+ public SerializedPdbImage ParentImage
+ {
+ get;
+ }
+
+ ///
+ /// Gets the parameters used for reading the data.
+ ///
+ public PdbReaderParameters Parameters
+ {
+ get;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs b/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs
new file mode 100644
index 000000000..7368424b0
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs
@@ -0,0 +1,33 @@
+namespace AsmResolver.Symbols.Pdb;
+
+///
+/// Provides parameters for configuring the reading process of a PDB image.
+///
+public class PdbReaderParameters
+{
+ ///
+ /// Creates new PDB reader parameters.
+ ///
+ public PdbReaderParameters()
+ : this(ThrowErrorListener.Instance)
+ {
+ }
+
+ ///
+ /// Creates new PDB reader parameters with the provided error listener object.
+ ///
+ /// The object used for receiving parser errors.
+ public PdbReaderParameters(IErrorListener errorListener)
+ {
+ ErrorListener = errorListener;
+ }
+
+ ///
+ /// Gets or sets the object responsible for receiving and processing parser errors.
+ ///
+ public IErrorListener ErrorListener
+ {
+ get;
+ set;
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs
new file mode 100644
index 000000000..5869c3acd
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs
@@ -0,0 +1,40 @@
+using AsmResolver.IO;
+using AsmResolver.Symbols.Pdb.Records.Serialized;
+
+namespace AsmResolver.Symbols.Pdb.Records;
+
+///
+/// Represents a single symbol record within the symbol record stream of a PDB file.
+///
+public abstract class CodeViewSymbol
+{
+ ///
+ /// Gets the type of symbol this record encodes.
+ ///
+ public abstract CodeViewSymbolType CodeViewSymbolType
+ {
+ get;
+ }
+
+ ///
+ /// Reads a single symbol record from the input stream.
+ ///
+ /// The reading context in which the symbol is situated in.
+ /// The input stream.
+ /// The read symbol.
+ public static CodeViewSymbol FromReader(PdbReaderContext context, ref BinaryStreamReader reader)
+ {
+ ushort length = reader.ReadUInt16();
+ var type = (CodeViewSymbolType) reader.ReadUInt16();
+ var dataReader = reader.ForkRelative(reader.RelativeOffset, (uint) (length - 2));
+ reader.Offset += (ulong) (length - 2);
+
+ return type switch
+ {
+ CodeViewSymbolType.Pub32 => new SerializedPublicSymbol(dataReader),
+ CodeViewSymbolType.Udt => new SerializedUserDefinedTypeSymbol(context, dataReader),
+ CodeViewSymbolType.Constant => new SerializedConstantSymbol(context, dataReader),
+ _ => new UnknownSymbol(type, dataReader.ReadToEnd())
+ };
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs
new file mode 100644
index 000000000..11bb6e3ad
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs
@@ -0,0 +1,865 @@
+namespace AsmResolver.Symbols.Pdb.Records;
+
+// Reference: microsoft-pdb/include/cvinfo.h
+
+///
+/// Provides members defining all symbol record types that can be stored in a PDB symbol stream.
+///
+public enum CodeViewSymbolType : ushort
+{
+#pragma warning disable CS1591
+ ///
+ /// Indicates the symbol is a Compile flags symbol
+ ///
+ Compile = 0x0001,
+
+ ///
+ /// Indicates the symbol is a Register variable
+ ///
+ Register16T = 0x0002,
+
+ ///
+ /// Indicates the symbol is a constant symbol
+ ///
+ Constant16T = 0x0003,
+
+ ///
+ /// Indicates the symbol is a User defined type
+ ///
+ Udt16T = 0x0004,
+
+ ///
+ /// Indicates the symbol is a Start Search
+ ///
+ SSearch = 0x0005,
+
+ ///
+ /// Indicates the symbol is a Block, procedure, "with" or thunk end
+ ///
+ End = 0x0006,
+
+ ///
+ /// Indicates the symbol is a Reserve symbol space in $$Symbols table
+ ///
+ Skip = 0x0007,
+
+ ///
+ /// Indicates the symbol is a Reserved symbol for CV internal use
+ ///
+ CVReserve = 0x0008,
+
+ ///
+ /// Indicates the symbol is a path to object file name
+ ///
+ ObjnameSt = 0x0009,
+
+ ///
+ /// Indicates the symbol is a end of argument/return list
+ ///
+ EndArg = 0x000a,
+
+ ///
+ /// Indicates the symbol is a special UDT for cobol that does not symbol pack
+ ///
+ CobolUdt16T = 0x000b,
+
+ ///
+ /// Indicates the symbol is a multiple register variable
+ ///
+ ManyReg16T = 0x000c,
+
+ ///
+ /// Indicates the symbol is a return description symbol
+ ///
+ Return = 0x000d,
+
+ ///
+ /// Indicates the symbol is a description of this pointer on entry
+ ///
+ EntryThis = 0x000e,
+
+ ///
+ /// Indicates the symbol is a BP-relative
+ ///
+ BPRel16 = 0x0100,
+
+ ///
+ /// Indicates the symbol is a Module-local symbol
+ ///
+ Ldata16 = 0x0101,
+
+ ///
+ /// Indicates the symbol is a Global data symbol
+ ///
+ GData16 = 0x0102,
+
+ ///
+ /// Indicates the symbol is a a public symbol
+ ///
+ Pub16 = 0x0103,
+
+ ///
+ /// Indicates the symbol is a Local procedure start
+ ///
+ LProc16 = 0x0104,
+
+ ///
+ /// Indicates the symbol is a Global procedure start
+ ///
+ GProc16 = 0x0105,
+
+ ///
+ /// Indicates the symbol is a Thunk Start
+ ///
+ Thunk16 = 0x0106,
+
+ ///
+ /// Indicates the symbol is a block start
+ ///
+ Block16 = 0x0107,
+
+ ///
+ /// Indicates the symbol is a with start
+ ///
+ With16 = 0x0108,
+
+ ///
+ /// Indicates the symbol is a code label
+ ///
+ Label16 = 0x0109,
+
+ ///
+ /// Indicates the symbol is a change execution model
+ ///
+ CexModel16 = 0x010a,
+
+ ///
+ /// Indicates the symbol is a address of virtual function table
+ ///
+ VFTable16 = 0x010b,
+
+ ///
+ /// Indicates the symbol is a register relative address
+ ///
+ RegRel16 = 0x010c,
+
+ ///
+ /// Indicates the symbol is a BP-relative
+ ///
+ BBRel3216T = 0x0200,
+
+ ///
+ /// Indicates the symbol is a Module-local symbol
+ ///
+ LData3216T = 0x0201,
+
+ ///
+ /// Indicates the symbol is a Global data symbol
+ ///
+ GData3216T = 0x0202,
+
+ ///
+ /// Indicates the symbol is a a public symbol (CV internal reserved)
+ ///
+ Pub3216T = 0x0203,
+
+ ///
+ /// Indicates the symbol is a Local procedure start
+ ///
+ LProc3216T = 0x0204,
+
+ ///
+ /// Indicates the symbol is a Global procedure start
+ ///
+ GProc3216T = 0x0205,
+
+ ///
+ /// Indicates the symbol is a Thunk Start
+ ///
+ Thunk32St = 0x0206,
+
+ ///
+ /// Indicates the symbol is a block start
+ ///
+ Block32St = 0x0207,
+
+ ///
+ /// Indicates the symbol is a with start
+ ///
+ With32St = 0x0208,
+
+ ///
+ /// Indicates the symbol is a code label
+ ///
+ Label32St = 0x0209,
+
+ ///
+ /// Indicates the symbol is a change execution model
+ ///
+ CexModel32 = 0x020a,
+
+ ///
+ /// Indicates the symbol is a address of virtual function table
+ ///
+ VFTable3216T = 0x020b,
+
+ ///
+ /// Indicates the symbol is a register relative address
+ ///
+ RegRel3216T = 0x020c,
+
+ ///
+ /// Indicates the symbol is a local thread storage
+ ///
+ LThread3216T = 0x020d,
+
+ ///
+ /// Indicates the symbol is a global thread storage
+ ///
+ GThread3216T = 0x020e,
+
+ ///
+ /// Indicates the symbol is a static link for MIPS EH implementation
+ ///
+ SLink32 = 0x020f,
+
+ ///
+ /// Indicates the symbol is a Local procedure start
+ ///
+ LProcMip16T = 0x0300,
+
+ ///
+ /// Indicates the symbol is a Global procedure start
+ ///
+ GProcMip16T = 0x0301,
+
+ // if these ref symbols have names following then the names are in ST format
+ ///
+ /// Indicates the symbol is a Reference to a procedure
+ ///
+ ProcRefSt = 0x0400,
+
+ ///
+ /// Indicates the symbol is a Reference to data
+ ///
+ DataRefSt = 0x0401,
+
+ ///
+ /// Indicates the symbol is a Used for page alignment of symbols
+ ///
+ Align = 0x0402,
+
+ ///
+ /// Indicates the symbol is a Local Reference to a procedure
+ ///
+ LProcRefSt = 0x0403,
+
+ ///
+ /// Indicates the symbol is a OEM defined symbol
+ ///
+ Oem = 0x0404,
+
+ // sym records with 32-bit types embedded instead of 16-bit
+ // all have 0x1000 bit set for easy identification
+ // only do the 32-bit target versions since we don't really
+ // care about 16-bit ones anymore.
+ // TI16_MAX = 0x1000,
+
+ ///
+ /// Indicates the symbol is a Register variable
+ ///
+ RegisterSt = 0x1001,
+
+ ///
+ /// Indicates the symbol is a constant symbol
+ ///
+ ConstantSt = 0x1002,
+
+ ///
+ /// Indicates the symbol is a User defined type
+ ///
+ UdtSt = 0x1003,
+
+ ///
+ /// Indicates the symbol is a special UDT for cobol that does not symbol pack
+ ///
+ CobolUdtSt = 0x1004,
+
+ ///
+ /// Indicates the symbol is a multiple register variable
+ ///
+ ManyRegSt = 0x1005,
+
+ ///
+ /// Indicates the symbol is a BP-relative
+ ///
+ BBRel32St = 0x1006,
+
+ ///
+ /// Indicates the symbol is a Module-local symbol
+ ///
+ LData32St = 0x1007,
+
+ ///
+ /// Indicates the symbol is a Global data symbol
+ ///
+ GData32St = 0x1008,
+
+ ///
+ /// Indicates the symbol is a a public symbol (CV internal reserved)
+ ///
+ Pub32St = 0x1009,
+
+ ///
+ /// Indicates the symbol is a Local procedure start
+ ///
+ LProc32St = 0x100a,
+
+ ///
+ /// Indicates the symbol is a Global procedure start
+ ///
+ GProc32St = 0x100b,
+
+ ///
+ /// Indicates the symbol is a address of virtual function table
+ ///
+ VFTable32 = 0x100c,
+
+ ///
+ /// Indicates the symbol is a register relative address
+ ///
+ RegRel32St = 0x100d,
+
+ ///
+ /// Indicates the symbol is a local thread storage
+ ///
+ LThread32St = 0x100e,
+
+ ///
+ /// Indicates the symbol is a global thread storage
+ ///
+ GThread32St = 0x100f,
+
+ ///
+ /// Indicates the symbol is a Local procedure start
+ ///
+ LProcMipSt = 0x1010,
+
+ ///
+ /// Indicates the symbol is a Global procedure start
+ ///
+ GProcMipSt = 0x1011,
+
+ ///
+ /// Indicates the symbol is a extra frame and proc information
+ ///
+ FrameProc = 0x1012,
+
+ ///
+ /// Indicates the symbol is a extended compile flags and info
+ ///
+ Compile2St = 0x1013,
+
+ ///
+ /// Indicates the symbol is a multiple register variable
+ ///
+ ManyReg2St = 0x1014,
+
+ ///
+ /// Indicates the symbol is a Local procedure start (IA64)
+ ///
+ LProcIa64St = 0x1015,
+
+ ///
+ /// Indicates the symbol is a Global procedure start (IA64)
+ ///
+ GProcIa64St = 0x1016,
+
+ ///
+ /// Indicates the symbol is a local IL sym with field for local slot index
+ ///
+ LocalSlotSt = 0x1017,
+
+ ///
+ /// Indicates the symbol is a local IL sym with field for parameter slot index
+ ///
+ ParamSlotSt = 0x1018,
+
+ ///
+ /// Indicates the symbol is a Annotation string literals
+ ///
+ Annotation = 0x1019,
+
+ ///
+ /// Indicates the symbol is a Global proc
+ ///
+ GManProcSt = 0x101a,
+
+ ///
+ /// Indicates the symbol is a Local proc
+ ///
+ LManProcSt = 0x101b,
+
+ ///
+ /// Reserved
+ ///
+ Reserved1 = 0x101c,
+
+ ///
+ /// Reserved
+ ///
+ Reserved2 = 0x101d,
+
+ ///
+ /// Reserved
+ ///
+ Reserved3 = 0x101e,
+
+ ///
+ /// Reserved
+ ///
+ RESERVED4 = 0x101f,
+
+ LManDataSt = 0x1020,
+
+ GManDataSt = 0x1021,
+
+ ManFrameRelSt = 0x1022,
+
+ ManRegisterSt = 0x1023,
+
+ ManSlotSt = 0x1024,
+
+ ManManyRegSt = 0x1025,
+
+ ManRegRelSt = 0x1026,
+
+ ManManyReg2St = 0x1027,
+
+ ///
+ /// Indicates the symbol is a Index for type referenced by name from metadata
+ ///
+ ManTypRef = 0x1028,
+
+ ///
+ /// Indicates the symbol is a Using namespace
+ ///
+ UNamespaceSt = 0x1029,
+
+ // Symbols w/ SZ name fields. All name fields contain utf8 encoded strings.
+ ///
+ /// Indicates the symbol is a starting point for SZ name symbols
+ ///
+ StMax = 0x1100,
+
+ ///
+ /// Indicates the symbol is a path to object file name
+ ///
+ ObjName = 0x1101,
+
+ ///
+ /// Indicates the symbol is a Thunk Start
+ ///
+ Thunk32 = 0x1102,
+
+ ///
+ /// Indicates the symbol is a block start
+ ///
+ Block32 = 0x1103,
+
+ ///
+ /// Indicates the symbol is a with start
+ ///
+ With32 = 0x1104,
+
+ ///
+ /// Indicates the symbol is a code label
+ ///
+ Label32 = 0x1105,
+
+ ///
+ /// Indicates the symbol is a Register variable
+ ///
+ Register = 0x1106,
+
+ ///
+ /// Indicates the symbol is a constant symbol
+ ///
+ Constant = 0x1107,
+
+ ///
+ /// Indicates the symbol is a User defined type
+ ///
+ Udt = 0x1108,
+
+ ///
+ /// Indicates the symbol is a special UDT for cobol that does not symbol pack
+ ///
+ CobolUdt = 0x1109,
+
+ ///
+ /// Indicates the symbol is a multiple register variable
+ ///
+ ManyReg = 0x110a,
+
+ ///
+ /// Indicates the symbol is a BP-relative
+ ///
+ BBRel32 = 0x110b,
+
+ ///
+ /// Indicates the symbol is a Module-local symbol
+ ///
+ LData32 = 0x110c,
+
+ ///
+ /// Indicates the symbol is a Global data symbol
+ ///
+ GData32 = 0x110d,
+
+ ///
+ /// Indicates the symbol is a a public symbol (CV internal reserved)
+ ///
+ Pub32 = 0x110e,
+
+ ///
+ /// Indicates the symbol is a Local procedure start
+ ///
+ LProc32 = 0x110f,
+
+ ///
+ /// Indicates the symbol is a Global procedure start
+ ///
+ GProc32 = 0x1110,
+
+ ///
+ /// Indicates the symbol is a register relative address
+ ///
+ RegRel32 = 0x1111,
+
+ ///
+ /// Indicates the symbol is a local thread storage
+ ///
+ LThread32 = 0x1112,
+
+ ///
+ /// Indicates the symbol is a global thread storage
+ ///
+ GThread32 = 0x1113,
+
+ ///
+ /// Indicates the symbol is a Local procedure start
+ ///
+ LProcMips = 0x1114,
+
+ ///
+ /// Indicates the symbol is a Global procedure start
+ ///
+ GProcMips = 0x1115,
+
+ ///
+ /// Indicates the symbol is a extended compile flags and info
+ ///
+ Compile2 = 0x1116,
+
+ ///
+ /// Indicates the symbol is a multiple register variable
+ ///
+ ManyReg2 = 0x1117,
+
+ ///
+ /// Indicates the symbol is a Local procedure start (IA64)
+ ///
+ LprocIa64 = 0x1118,
+
+ ///
+ /// Indicates the symbol is a Global procedure start (IA64)
+ ///
+ GProcIa64 = 0x1119,
+
+ ///
+ /// Indicates the symbol is a local IL sym with field for local slot index
+ ///
+ LocalSlot = 0x111a,
+
+ ///
+ /// Indicates the symbol is a alias for LOCALSLOT
+ ///
+ Slot = LocalSlot,
+
+ ///
+ /// Indicates the symbol is a local IL sym with field for parameter slot index
+ ///
+ ParamSlot = 0x111b,
+
+ // symbols to support managed code debugging
+ LManData = 0x111c,
+ GManData = 0x111d,
+ ManFrameRel = 0x111e,
+ ManRegister = 0x111f,
+ ManSlot = 0x1120,
+ ManManyReg = 0x1121,
+ ManRegRel = 0x1122,
+ ManManyReg2 = 0x1123,
+
+ ///
+ /// Indicates the symbol is a Using namespace
+ ///
+ UNamespace = 0x1124,
+
+ // ref symbols with name fields
+ ///
+ /// Indicates the symbol is a Reference to a procedure
+ ///
+ ProcRef = 0x1125,
+
+ ///
+ /// Indicates the symbol is a Reference to data
+ ///
+ DataRef = 0x1126,
+
+ ///
+ /// Indicates the symbol is a Local Reference to a procedure
+ ///
+ LProcRef = 0x1127,
+
+ ///
+ /// Indicates the symbol is a Reference to an ANNOTATION symbol
+ ///
+ AnnotationRef = 0x1128,
+
+ ///
+ /// Indicates the symbol is a Reference to one of the many MANPROCSYM's
+ ///
+ TokenRef = 0x1129,
+
+ // continuation of managed symbols
+ ///
+ /// Indicates the symbol is a Global proc
+ ///
+ GManProc = 0x112a,
+
+ ///
+ /// Indicates the symbol is a Local proc
+ ///
+ LManProc = 0x112b,
+
+ // short, light-weight thunks
+ ///
+ /// Indicates the symbol is a trampoline thunks
+ ///
+ Trampoline = 0x112c,
+
+ ///
+ /// Indicates the symbol is a constants with metadata type info
+ ///
+ ManConstant = 0x112d,
+
+ // native attributed local/parms
+ ///
+ /// Indicates the symbol is a relative to virtual frame ptr
+ ///
+ AttrFrameRel = 0x112e,
+
+ ///
+ /// Indicates the symbol is a stored in a register
+ ///
+ AttrRegister = 0x112f,
+
+ ///
+ /// Indicates the symbol is a relative to register (alternate frame ptr)
+ ///
+ AttrRegRel = 0x1130,
+
+ ///
+ /// Indicates the symbol is a stored in >1 register
+ ///
+ AttrManyReg = 0x1131,
+
+ // Separated code (from the compiler) support
+ SepCode = 0x1132,
+
+ ///
+ /// Indicates the symbol is a defines a local symbol in optimized code
+ ///
+ Local2005 = 0x1133,
+
+ ///
+ /// Indicates the symbol is a defines a single range of addresses in which symbol can be evaluated
+ ///
+ DefRange2005 = 0x1134,
+
+ ///
+ /// Indicates the symbol is a defines ranges of addresses in which symbol can be evaluated
+ ///
+ DefRange22005 = 0x1135,
+
+ ///
+ /// Indicates the symbol is a A COFF section in a PE executable
+ ///
+ Section = 0x1136,
+
+ ///
+ /// Indicates the symbol is a A COFF group
+ ///
+ CoffGroup = 0x1137,
+
+ ///
+ /// Indicates the symbol is a A export
+ ///
+ Export = 0x1138,
+
+ ///
+ /// Indicates the symbol is a Indirect call site information
+ ///
+ CallSiteInfo = 0x1139,
+
+ ///
+ /// Indicates the symbol is a Security cookie information
+ ///
+ FrameCookie = 0x113a,
+
+ ///
+ /// Indicates the symbol is a Discarded by LINK /OPT:REF (experimental, see richards)
+ ///
+ Discarded = 0x113b,
+
+ ///
+ /// Indicates the symbol is a Replacement for COMPILE2
+ ///
+ Compile3 = 0x113c,
+
+ ///
+ /// Indicates the symbol is a Environment block split off from COMPILE2
+ ///
+ EnvBlock = 0x113d,
+
+ ///
+ /// Indicates the symbol is a defines a local symbol in optimized code
+ ///
+ Local = 0x113e,
+
+ ///
+ /// Indicates the symbol is a defines a single range of addresses in which symbol can be evaluated
+ ///
+ DefRange = 0x113f,
+
+ ///
+ /// Indicates the symbol is a ranges for a subfield
+ ///
+ DefRangeSubField = 0x1140,
+
+ ///
+ /// Indicates the symbol is a ranges for en-registered symbol
+ ///
+ DefRangeRegister = 0x1141,
+
+ ///
+ /// Indicates the symbol is a range for stack symbol.
+ ///
+ DefRangeFramePointerRel = 0x1142,
+
+ ///
+ /// Indicates the symbol is a ranges for en-registered field of symbol
+ ///
+ DefRangeSubFieldRegister = 0x1143,
+
+ ///
+ /// Indicates the symbol is a range for stack symbol span valid full scope of function body, gap might apply.
+ ///
+ DefRangeFramePointerRelFullScope = 0x1144,
+
+ ///
+ /// Indicates the symbol is a range for symbol address as register + offset.
+ ///
+ DefRangeRegisterRel = 0x1145,
+
+ // PROC symbols that reference ID instead of type
+ LProc32Id = 0x1146,
+ GProc32Id = 0x1147,
+ LProcMipId = 0x1148,
+ GProcMipId = 0x1149,
+ LProcIa64Id = 0x114a,
+ GProcIa64Id = 0x114b,
+
+ ///
+ /// Indicates the symbol is a build information.
+ ///
+ BuildInfo = 0x114c,
+
+ ///
+ /// Indicates the symbol is a inlined function callsite.
+ ///
+ InlineSite = 0x114d,
+ InlineSiteEnd = 0x114e,
+ ProcIdEnd = 0x114f,
+
+ DefRangeHlsl = 0x1150,
+ GDataHlsl = 0x1151,
+ LDataHlsl = 0x1152,
+
+ FileStatic = 0x1153,
+
+ ///
+ /// Indicates the symbol is a DPC groupshared variable
+ ///
+ LocalDpcGroupShared = 0x1154,
+
+ ///
+ /// Indicates the symbol is a DPC local procedure start
+ ///
+ LProc32Dpc = 0x1155,
+ LProc32DpcId = 0x1156,
+
+ ///
+ /// Indicates the symbol is a DPC pointer tag definition range
+ ///
+ DefRangeDpcPtrTag = 0x1157,
+
+ ///
+ /// Indicates the symbol is a DPC pointer tag value to symbol record map
+ ///
+ DpcSymTagMap = 0x1158,
+
+ ArmSwitchTable = 0x1159,
+ Callees = 0x115a,
+ Callers = 0x115b,
+ PogoData = 0x115c,
+
+ ///
+ /// Indicates the symbol is a extended inline site information
+ ///
+ InlineSite2 = 0x115d,
+
+ ///
+ /// Indicates the symbol is a heap allocation site
+ ///
+ HeapAllocSite = 0x115e,
+
+ ///
+ /// Indicates the symbol is a only generated at link time
+ ///
+ ModTypeRef = 0x115f,
+
+ ///
+ /// Indicates the symbol is a only generated at link time for mini PDB
+ ///
+ RefMiniPdb = 0x1160,
+
+ ///
+ /// Indicates the symbol is a only generated at link time for mini PDB
+ ///
+ PdbMap = 0x1161,
+
+ GDataHlsl32 = 0x1162,
+ LDataHlsl32 = 0x1163,
+
+ GDataHlsl32Ex = 0x1164,
+ LDataHlsl32Ex = 0x1165,
+
+ RecTypeMax, // one greater than last
+ RecTypeLast = RecTypeMax - 1,
+ RecTypePad = RecTypeMax + 0x100 // Used *only* to verify symbol record types so that current PDB code can potentially read
+ // future PDBs (assuming no format change, etc).
+#pragma warning restore CS1591
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs
new file mode 100644
index 000000000..5bdc45c4c
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs
@@ -0,0 +1,85 @@
+using AsmResolver.Symbols.Pdb.Leaves;
+
+namespace AsmResolver.Symbols.Pdb.Records;
+
+///
+/// Represents a single constant symbol.
+///
+public class ConstantSymbol : CodeViewSymbol
+{
+ private readonly LazyVariable _name;
+ private readonly LazyVariable _type;
+
+ ///
+ /// Initializes a named constant
+ ///
+ protected ConstantSymbol()
+ {
+ _name = new LazyVariable(GetName);
+ _type = new LazyVariable(GetConstantType);
+ }
+
+ ///
+ /// Defines a new named constant.
+ ///
+ /// The name of the type.
+ /// The type.
+ /// The value to assign to the constant.
+ public ConstantSymbol(Utf8String name, CodeViewTypeRecord type, ushort value)
+ {
+ _name = new LazyVariable(name);
+ _type = new LazyVariable(type);
+ Value = value;
+ }
+
+ ///
+ public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Constant;
+
+ ///
+ /// Gets or sets the value type of the constant.
+ ///
+ public CodeViewTypeRecord Type
+ {
+ get => _type.Value;
+ set => _type.Value = value;
+ }
+
+ ///
+ /// Gets or sets the numerical value assigned to the constant.
+ ///
+ public ushort Value
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the name of the constant.
+ ///
+ public Utf8String Name
+ {
+ get => _name.Value;
+ set => _name.Value = value;
+ }
+
+ ///
+ /// Obtains the name of the constant.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual Utf8String GetName() => Utf8String.Empty;
+
+ ///
+ /// Obtains the value type of the constant.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetConstantType() => null;
+
+ ///
+ public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name} = {Value}";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs
new file mode 100644
index 000000000..08515a42b
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs
@@ -0,0 +1,123 @@
+namespace AsmResolver.Symbols.Pdb.Records;
+
+///
+/// Represents a public symbol stored in a PDB symbol stream.
+///
+public class PublicSymbol : CodeViewSymbol
+{
+ private readonly LazyVariable _name;
+
+ ///
+ /// Initializes a new empty public symbol.
+ ///
+ protected PublicSymbol()
+ {
+ _name = new LazyVariable(GetName);
+ }
+
+ ///
+ /// Creates a new public symbol.
+ ///
+ /// The segment index.
+ /// The offset within the segment the symbol starts at.
+ /// The name of the symbol.
+ /// The attributes associated to the symbol.
+ public PublicSymbol(ushort segment, uint offset, Utf8String name, PublicSymbolAttributes attributes)
+ {
+ Segment = segment;
+ Offset = offset;
+ _name = new LazyVariable(name);
+ Attributes = attributes;
+ }
+
+ ///
+ public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Pub32;
+
+ ///
+ /// Gets or sets the file segment index this symbol is located in.
+ ///
+ public ushort Segment
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the offset within the file that this symbol is defined at.
+ ///
+ public uint Offset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets attributes associated to the public symbol.
+ ///
+ public PublicSymbolAttributes Attributes
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the symbol is a code symbol.
+ ///
+ public bool IsCode
+ {
+ get => (Attributes & PublicSymbolAttributes.Code) != 0;
+ set => Attributes = (Attributes & ~PublicSymbolAttributes.Code)
+ | (value ? PublicSymbolAttributes.Code : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the symbol is a function symbol.
+ ///
+ public bool IsFunction
+ {
+ get => (Attributes & PublicSymbolAttributes.Function) != 0;
+ set => Attributes = (Attributes & ~PublicSymbolAttributes.Function)
+ | (value ? PublicSymbolAttributes.Function : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the symbol involves managed code.
+ ///
+ public bool IsManaged
+ {
+ get => (Attributes & PublicSymbolAttributes.Managed) != 0;
+ set => Attributes = (Attributes & ~PublicSymbolAttributes.Managed)
+ | (value ? PublicSymbolAttributes.Managed : 0);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the symbol involves MSIL code.
+ ///
+ public bool IsMsil
+ {
+ get => (Attributes & PublicSymbolAttributes.Msil) != 0;
+ set => Attributes = (Attributes & ~PublicSymbolAttributes.Msil)
+ | (value ? PublicSymbolAttributes.Msil : 0);
+ }
+
+ ///
+ /// Gets or sets the name of the symbol.
+ ///
+ public Utf8String Name
+ {
+ get => _name.Value;
+ set => _name.Value = value;
+ }
+
+ ///
+ /// Obtains the name of the public symbol.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual Utf8String GetName() => Utf8String.Empty;
+
+ ///
+ public override string ToString() => $"{CodeViewSymbolType}: [{Segment:X4}:{Offset:X8}] {Name}";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs
new file mode 100644
index 000000000..a5e5824bd
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace AsmResolver.Symbols.Pdb.Records;
+
+///
+/// Provides members defining all flags that can be associated to a public symbol.
+///
+[Flags]
+public enum PublicSymbolAttributes : uint
+{
+ ///
+ /// Indicates no flags are assigned to the symbol.
+ ///
+ None = 0,
+
+ ///
+ /// Indicates the symbol is a code symbol.
+ ///
+ Code = 0x00000001,
+
+ ///
+ /// Indicates the symbol is a function.
+ ///
+ Function = 0x00000002,
+
+ ///
+ /// Indicates the symbol involves managed code.
+ ///
+ Managed = 0x00000004,
+
+ ///
+ /// Indicates the symbol involves MSIL code.
+ ///
+ Msil = 0x00000008,
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs
new file mode 100644
index 000000000..3c3c31c53
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs
@@ -0,0 +1,39 @@
+using AsmResolver.IO;
+using AsmResolver.Symbols.Pdb.Leaves;
+
+namespace AsmResolver.Symbols.Pdb.Records.Serialized;
+
+///
+/// Represents a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedConstantSymbol : ConstantSymbol
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _typeIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads a constant symbol from the provided input stream.
+ ///
+ /// The reading context in which the symbol is situated in.
+ /// The input stream to read from.
+ public SerializedConstantSymbol(PdbReaderContext context, BinaryStreamReader reader)
+ {
+ _context = context;
+ _typeIndex = reader.ReadUInt32();
+ Value = reader.ReadUInt16();
+ _nameReader = reader;
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override CodeViewTypeRecord? GetConstantType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"Constant contains an invalid type index {_typeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs
new file mode 100644
index 000000000..f1a815036
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs
@@ -0,0 +1,26 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.Symbols.Pdb.Records.Serialized;
+
+///
+/// Represents a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedPublicSymbol : PublicSymbol
+{
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads a public symbol from the provided input stream.
+ ///
+ /// The input stream to read from.
+ public SerializedPublicSymbol(BinaryStreamReader reader)
+ {
+ Attributes = (PublicSymbolAttributes) reader.ReadUInt32();
+ Offset = reader.ReadUInt32();
+ Segment = reader.ReadUInt16();
+ _nameReader = reader;
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs
new file mode 100644
index 000000000..fdcce9a7f
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs
@@ -0,0 +1,38 @@
+using AsmResolver.IO;
+using AsmResolver.Symbols.Pdb.Leaves;
+
+namespace AsmResolver.Symbols.Pdb.Records.Serialized;
+
+///
+/// Represents a lazily initialized implementation of that is read from a PDB image.
+///
+public class SerializedUserDefinedTypeSymbol : UserDefinedTypeSymbol
+{
+ private readonly PdbReaderContext _context;
+ private readonly uint _typeIndex;
+ private readonly BinaryStreamReader _nameReader;
+
+ ///
+ /// Reads a user-defined type symbol from the provided input stream.
+ ///
+ /// The reading context in which the symbol is situated in.
+ /// The input stream to read from.
+ public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamReader reader)
+ {
+ _context = context;
+ _typeIndex = reader.ReadUInt32();
+ _nameReader = reader;
+ }
+
+ ///
+ protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String();
+
+ ///
+ protected override CodeViewTypeRecord? GetSymbolType()
+ {
+ return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type
+ ? type
+ : _context.Parameters.ErrorListener.BadImageAndReturn(
+ $"User-defined type contains an invalid type index {_typeIndex:X8}.");
+ }
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs
new file mode 100644
index 000000000..983173876
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs
@@ -0,0 +1,35 @@
+namespace AsmResolver.Symbols.Pdb.Records;
+
+///
+/// Represents a symbol record for which the format is unknown or unsupported.
+///
+public class UnknownSymbol : CodeViewSymbol
+{
+ ///
+ /// Creates a new unknown symbol record.
+ ///
+ /// The type of symbol.
+ /// The raw data stored in the record.
+ public UnknownSymbol(CodeViewSymbolType codeViewSymbolType, byte[] data)
+ {
+ CodeViewSymbolType = codeViewSymbolType;
+ Data = data;
+ }
+
+ ///
+ public override CodeViewSymbolType CodeViewSymbolType
+ {
+ get;
+ }
+
+ ///
+ /// Gets the raw data stored in the record.
+ ///
+ public byte[] Data
+ {
+ get;
+ }
+
+ ///
+ public override string ToString() => $"{CodeViewSymbolType.ToString()} ({Data.Length.ToString()} bytes)";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs
new file mode 100644
index 000000000..4e84a3aa3
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs
@@ -0,0 +1,74 @@
+using AsmResolver.Symbols.Pdb.Leaves;
+
+namespace AsmResolver.Symbols.Pdb.Records;
+
+///
+/// Represents a user-defined type symbol in a PDB symbol stream, mapping a symbol to a type in the TPI stream.
+///
+public class UserDefinedTypeSymbol : CodeViewSymbol
+{
+ private readonly LazyVariable _name;
+ private readonly LazyVariable _type;
+
+ ///
+ /// Initializes a new empty user-defined type symbol.
+ ///
+ protected UserDefinedTypeSymbol()
+ {
+ _name = new LazyVariable(GetName);
+ _type = new LazyVariable(GetSymbolType);
+ }
+
+ ///
+ /// Defines a new user-defined type.
+ ///
+ /// The name of the type.
+ /// The type.
+ public UserDefinedTypeSymbol(Utf8String name, CodeViewTypeRecord type)
+ {
+ _name = new LazyVariable(name);
+ _type = new LazyVariable(type);
+ }
+
+ ///
+ public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Udt;
+
+ ///
+ /// Gets or sets the name of the type.
+ ///
+ public Utf8String Name
+ {
+ get => _name.Value;
+ set => _name.Value = value;
+ }
+
+ ///
+ /// Gets or sets the index associated to the type.
+ ///
+ public CodeViewTypeRecord Type
+ {
+ get => _type.Value;
+ set => _type.Value = value;
+ }
+
+ ///
+ /// Obtains the new name of the type.
+ ///
+ /// The name.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual Utf8String GetName() => Utf8String.Empty;
+
+ ///
+ /// Obtains the type that is referenced by this symbol.
+ ///
+ /// The type.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual CodeViewTypeRecord? GetSymbolType() => null;
+
+ ///
+ public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name}";
+}
diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs
new file mode 100644
index 000000000..32bc7dcd6
--- /dev/null
+++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using AsmResolver.Symbols.Pdb.Leaves;
+using AsmResolver.Symbols.Pdb.Metadata.Dbi;
+using AsmResolver.Symbols.Pdb.Metadata.Info;
+using AsmResolver.Symbols.Pdb.Metadata.Tpi;
+using AsmResolver.Symbols.Pdb.Msf;
+using AsmResolver.Symbols.Pdb.Records;
+
+namespace AsmResolver.Symbols.Pdb;
+
+///
+/// Provides an implementation for a PDB image that is read from an input MSF file.
+///
+public class SerializedPdbImage : PdbImage
+{
+ private const int MinimalRequiredStreamCount = 5;
+ private readonly MsfFile _file;
+ private CodeViewLeaf?[]? _leaves;
+
+ ///
+ /// Interprets a PDB image from the provided MSF file.
+ ///
+ /// The MSF file to read from.
+ /// The parameters to use while reading the PDB image.
+ public SerializedPdbImage(MsfFile file, PdbReaderParameters readerParameters)
+ {
+ _file = file;
+
+ if (file.Streams.Count < MinimalRequiredStreamCount)
+ throw new BadImageFormatException("MSF does not contain the minimal required amount of streams.");
+
+ InfoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader());
+ DbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader());
+ TpiStream = TpiStream.FromReader(file.Streams[TpiStream.StreamIndex].CreateReader());
+
+ ReaderContext = new PdbReaderContext(this, readerParameters);
+ }
+
+ internal PdbReaderContext ReaderContext
+ {
+ get;
+ }
+
+ internal InfoStream InfoStream
+ {
+ get;
+ }
+
+ internal DbiStream DbiStream
+ {
+ get;
+ }
+
+ internal TpiStream TpiStream
+ {
+ get;
+ }
+
+ [MemberNotNull(nameof(_leaves))]
+ private void EnsureLeavesInitialized()
+ {
+ if (_leaves is null)
+ {
+ Interlocked.CompareExchange(ref _leaves,
+ new CodeViewLeaf?[TpiStream.TypeIndexEnd - TpiStream.TypeIndexBegin], null);
+ }
+ }
+
+ ///
+ public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf)
+ {
+ if (typeIndex < TpiStream.TypeIndexBegin)
+ return base.TryGetLeafRecord(typeIndex, out leaf);
+
+ EnsureLeavesInitialized();
+
+ if (typeIndex >= TpiStream.TypeIndexBegin && typeIndex < TpiStream.TypeIndexEnd)
+ {
+ leaf = _leaves[typeIndex - TpiStream.TypeIndexBegin];
+ if (leaf is null && TpiStream.TryGetLeafRecordReader(typeIndex, out var reader))
+ {
+ leaf = CodeViewLeaf.FromReader(ReaderContext, typeIndex, ref reader);
+ Interlocked.CompareExchange(ref _leaves[typeIndex - TpiStream.TypeIndexBegin], leaf, null);
+ }
+
+ leaf = _leaves[typeIndex - TpiStream.TypeIndexBegin];
+ return leaf is not null;
+ }
+
+ leaf = null;
+ return false;
+ }
+
+ ///
+ protected override IList GetSymbols()
+ {
+ var result = new List();
+
+ int index = DbiStream.SymbolRecordStreamIndex;
+ if (index >= _file.Streams.Count)
+ return result;
+
+ var reader = _file.Streams[DbiStream.SymbolRecordStreamIndex].CreateReader();
+ while (reader.CanRead(sizeof(ushort) * 2))
+ result.Add(CodeViewSymbol.FromReader(ReaderContext, ref reader));
+
+ return result;
+ }
+}
diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj
index 07dbf2281..b7685bc8a 100644
--- a/src/AsmResolver/AsmResolver.csproj
+++ b/src/AsmResolver/AsmResolver.csproj
@@ -22,7 +22,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/AsmResolver/Collections/BitList.cs b/src/AsmResolver/Collections/BitList.cs
new file mode 100644
index 000000000..c4224cd04
--- /dev/null
+++ b/src/AsmResolver/Collections/BitList.cs
@@ -0,0 +1,251 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace AsmResolver.Collections
+{
+ ///
+ /// Represents a bit vector that can be resized dynamically.
+ ///
+ public class BitList : IList
+ {
+ private const int WordSize = sizeof(int) * 8;
+ private uint[] _words;
+ private int _version;
+
+ ///
+ /// Creates a new bit list.
+ ///
+ public BitList()
+ {
+ _words = new uint[1];
+ }
+
+ ///
+ /// Creates a new bit list.
+ ///
+ /// The initial number of bits that the buffer should at least be able to store.
+ public BitList(int capacity)
+ {
+ _words = new uint[((uint) capacity).Align(WordSize)];
+ }
+
+ ///
+ public int Count
+ {
+ get;
+ private set;
+ }
+
+ ///
+ public bool IsReadOnly => false;
+
+ ///
+ public bool this[int index]
+ {
+ get
+ {
+ if (index >= Count)
+ throw new IndexOutOfRangeException();
+
+ (int wordIndex, int bitIndex) = SplitWordBitIndex(index);
+ return (_words[wordIndex] >> bitIndex & 1) != 0;
+ }
+ set
+ {
+ if (index >= Count)
+ throw new IndexOutOfRangeException();
+
+ (int wordIndex, int bitIndex) = SplitWordBitIndex(index);
+ _words[wordIndex] = (_words[wordIndex] & ~(1u << bitIndex)) | (value ? 1u << bitIndex : 0u);
+ _version++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static (int wordIndex, int bitIndex) SplitWordBitIndex(int index)
+ {
+ int wordIndex = Math.DivRem(index, WordSize, out int offset);
+ return (wordIndex, offset);
+ }
+
+ ///
+ public void Add(bool item)
+ {
+ EnsureCapacity(Count + 1);
+ Count++;
+ this[Count - 1] = item;
+ _version++;
+ }
+
+ ///
+ public void Clear() => Count = 0;
+
+ ///
+ public bool Contains(bool item) => IndexOf(item) != -1;
+
+ ///
+ public void CopyTo(bool[] array, int arrayIndex)
+ {
+ for (int i = 0; i < Count; i++)
+ array[arrayIndex + i] = this[i];
+ }
+
+ ///
+ public bool Remove(bool item)
+ {
+ int index = IndexOf(item);
+ if (index == -1)
+ return false;
+
+ RemoveAt(index);
+ return true;
+ }
+
+ ///
+ public int IndexOf(bool item)
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ (int wordIndex, int bitIndex) = SplitWordBitIndex(i);
+ if ((_words[wordIndex] >> bitIndex & 1) != 0 == item)
+ return i;
+ }
+
+ return -1;
+ }
+
+ ///
+ public void Insert(int index, bool item)
+ {
+ if (index > Count)
+ throw new IndexOutOfRangeException();
+
+ EnsureCapacity(Count++);
+ (int wordIndex, int bitIndex) = SplitWordBitIndex(index);
+
+ uint carry = _words[wordIndex] & (1u << (WordSize - 1));
+
+ // Insert bit into current word.
+ uint lowerMask = (1u << bitIndex) - 1;
+ uint upperMask = ~lowerMask;
+ _words[wordIndex] = (_words[wordIndex] & upperMask) << 1 // Shift left-side of the bit index by one
+ | (item ? 1u << bitIndex : 0u) // Insert bit.
+ | (_words[wordIndex] & lowerMask); // Keep right-side of the bit.
+
+ for (int i = wordIndex + 1; i < _words.Length; i++)
+ {
+ uint nextCarry = _words[i] & (1u << (WordSize - 1));
+ _words[i] = (_words[i] << 1) | (carry >> (WordSize - 1));
+ carry = nextCarry;
+ }
+
+ _version++;
+ }
+
+ ///
+ public void RemoveAt(int index)
+ {
+ Count--;
+ (int wordIndex, int bitIndex) = SplitWordBitIndex(index);
+
+ // Note we check both word count and actual bit count. Words in the buffer might contain garbage data for
+ // every bit index i >= Count. Also, there might be exactly enough words allocated for Count bits, i.e.
+ // there might not be a "next" word.
+ uint borrow = wordIndex + 1 < _words.Length && ((uint) index).Align(WordSize) < Count
+ ? _words[wordIndex + 1] & 1
+ : 0;
+
+ uint lowerMask = (1u << bitIndex) - 1;
+ uint upperMask = ~((1u << (bitIndex + 1)) - 1);
+ _words[wordIndex] = (_words[wordIndex] & upperMask) >> 1 // Shift left-side of the bit index by one
+ | (_words[wordIndex] & lowerMask) // Keep right-side of the bit.
+ | borrow << (WordSize - 1); // Copy first bit of next word into last bit of current.
+
+ for (int i = wordIndex + 1; i < _words.Length; i++)
+ {
+ uint nextBorrow = i + 1 < _words.Length && ((uint) index).Align(WordSize) < Count
+ ? _words[i + 1] & 1
+ : 0;
+
+ _words[i] = (_words[i] >> 1) | (borrow << (WordSize - 1));
+ borrow = nextBorrow;
+ }
+
+ _version++;
+ }
+
+ ///
+ /// Ensures the provided number of bits can be stored in the bit list.
+ ///
+ /// The number of bits to store in the list.
+ public void EnsureCapacity(int capacity)
+ {
+ if (capacity < WordSize * _words.Length)
+ return;
+
+ int newWordCount = (int) (((uint) capacity).Align(WordSize) / 8);
+ Array.Resize(ref _words, newWordCount);
+ }
+
+ ///
+ /// Returns an enumerator for all bits in the bit vector.
+ ///
+ /// The enumerator.
+ public Enumerator GetEnumerator() => new(this);
+
+ ///
+ IEnumerator IEnumerable