diff --git a/AsmResolver.sln b/AsmResolver.sln
index c8f471ef6..0cf5601e0 100644
--- a/AsmResolver.sln
+++ b/AsmResolver.sln
@@ -86,6 +86,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
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
@@ -424,6 +432,54 @@ Global
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x64.Build.0 = Release|Any CPU
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x86.ActiveCfg = Release|Any CPU
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x86.Build.0 = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x64.Build.0 = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x86.Build.0 = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x64.ActiveCfg = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x64.Build.0 = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x86.ActiveCfg = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x86.Build.0 = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x64.Build.0 = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x86.Build.0 = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.ActiveCfg = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.Build.0 = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.ActiveCfg = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.Build.0 = Release|Any CPU
+ {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
@@ -461,6 +517,10 @@ Global
{40483E28-C703-4933-BA5B-9512EF6E6A21} = {EA971BB0-94BA-44DB-B16C-212D2DB27E17}
{CF6A7E02-37DC-4963-AC14-76D74ADCD87A} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0}
{2D1DF5DA-7367-4490-B3F0-B996348E150B} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0}
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287} = {34A95168-A162-4F6A-803B-B6F221FE9EA6}
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE} = {786C1732-8C96-45DD-97BB-639C9AA7F45B}
+ {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/Directory.Build.props b/Directory.Build.props
index a2c6826c2..da8160f3a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -7,7 +7,7 @@
https://github.com/Washi1337/AsmResolvergit10
- 4.11.2
+ 5.0.0-beta.1
diff --git a/appveyor.yml b/appveyor.yml
index 64299e25e..7a1ff9bec 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -4,7 +4,7 @@
- master
image: Visual Studio 2022
- version: 4.11.2-master-build.{build}
+ version: 5.0.0-master-build.{build}
configuration: Release
skip_commits:
@@ -33,7 +33,7 @@
- development
image: Visual Studio 2022
- version: 4.11.2-dev-build.{build}
+ version: 5.0.0-dev-build.{build}
configuration: Release
skip_commits:
diff --git a/docs/dotnet/cloning.rst b/docs/dotnet/cloning.rst
index 540d448d9..871f6b499 100644
--- a/docs/dotnet/cloning.rst
+++ b/docs/dotnet/cloning.rst
@@ -13,22 +13,22 @@ To help developers in injecting existing code into a module, ``AsmResolver.DotNe
The MemberCloner class
----------------------
-The ``MemberCloner`` is the root object responsible for cloning members in a .NET module, and importing them into another.
+The ``MemberCloner`` is the root object responsible for cloning members in a .NET module, and importing them into another.
In the snippet below, we define a new ``MemberCloner`` that is able to clone and import members into the module ``destinationModule:``.
.. code-block:: csharp
ModuleDefinition destinationModule = ...
- MemberCloner cloner = new MemberCloner(destinationModule);
+ var cloner = new MemberCloner(destinationModule);
In the remaining sections of this article, we assume that the ``MemberCloner`` is initialized using the code above.
-Include members to clone
-------------------------
+Include members
+---------------
-The general idea of the ``MemberCloner`` is to first provide all the members to be cloned, and then clone everything all in one go. The reason why it is done like this, is to allow the ``MemberCloner`` to fix up any cross references to members within the to-be-cloned metadata and CIL code.
+The general idea of the ``MemberCloner`` is to first provide all the members to be cloned, and then clone everything all in one go. This is to allow the ``MemberCloner`` to fix up any cross references to members within the to-be-cloned metadata and CIL code.
For the sake of the example, we assume that the following two classes are to be injected in ``destinationModule``:
@@ -36,7 +36,7 @@ For the sake of the example, we assume that the following two classes are to be
public class Rectangle
{
- public Rectangle(Vector2 location, Vector2 size)
+ public Rectangle(Vector2 location, Vector2 size)
{
Location = location;
Size = size;
@@ -50,7 +50,7 @@ For the sake of the example, we assume that the following two classes are to be
public class Vector2
{
- public Vector2(int x, int y)
+ public Vector2(int x, int y)
{
X = x;
Y = y;
@@ -60,7 +60,7 @@ For the sake of the example, we assume that the following two classes are to be
public int Y { get; set; }
}
-The first thing we then should do, is find the type definitions that correspond to these classes:
+The first step in cloning involves loading the source module, and finding the type definitions that correspond to these classes:
.. code-block:: csharp
@@ -68,6 +68,7 @@ The first thing we then should do, is find the type definitions that correspond
var rectangleType = sourceModule.TopLevelTypes.First(t => t.Name == "Rectangle");
var vectorType = sourceModule.TopLevelTypes.First(t => t.Name == "Vector2");
+
Alternatively, if the source assembly is loaded by the CLR, we also can look up the members by metadata token.
.. code-block:: csharp
@@ -84,41 +85,147 @@ We can then use ``MemberCloner.Include`` to include the types in the cloning pro
cloner.Include(rectangleType, recursive: true);
cloner.Include(vectorType, recursive: true);
-The ``recursive`` parameter indicates whether all members and nested types need to be included as well.
+
+The ``recursive`` parameter indicates whether all members and nested types need to be included as well. This value is ``true`` by default and can also be omitted.
+
+.. code-block:: csharp
+
+ cloner.Include(rectangleType);
+ cloner.Include(vectorType);
+
``Include`` returns the same ``MemberCloner`` instance. It is therefore also possible to create a long method chain of members to include in the cloning process.
.. code-block:: csharp
cloner
- .Include(rectangleType, recursive: true)
- .Include(vectorType, recursive: true);
+ .Include(rectangleType)
+ .Include(vectorType);
+
Cloning individual methods, fields, properties and/or events is also supported. This can be done by including the corresponding ``MethodDefinition``, ``FieldDefinition``, ``PropertyDefinition`` and/or ``EventDefinition`` instead.
-Cloning the included members
+Cloning the included members
----------------------------
-When all members are included, it is possible to call ``MemberCloner.Clone`` to clone them all in one go.
+When all members are included, it is possible to call ``MemberCloner.Clone`` to clone them all in one go.
.. code-block:: csharp
var result = cloner.Clone();
-The ``MemberCloner`` will automatically resolve any cross references between types, fields and methods that are included in the cloning process.
+
+The ``MemberCloner`` will automatically resolve any cross references between types, fields and methods that are included in the cloning process.
For instance, going with the example in the previous section, if both the ``Rectangle`` as well as the ``Vector2`` classes are included, any reference in ``Rectangle`` to ``Vector2`` will be replaced with a reference to the cloned ``Vector2``. If not all members are included, the ``MemberCloner`` will assume that these are references to external libraries, and will use the ``ReferenceImporter`` to construct references to these members instead.
-.. warning::
- The ``MemberCloner`` heavily depends on the ``ReferenceImporter`` class for copying references into the destination module. This class has some limitations, in particular on importing / cloning from modules targeting different framework versions. See :ref:`dotnet-importer-common-caveats` for more information.
+Custom reference importers
+--------------------------
+
+The ``MemberCloner`` heavily depends on the ``CloneContextAwareReferenceImporter`` class for copying references into the destination module. This class is derived from ``ReferenceImporter``, which has some limitations. In particular, limitations arise when cloning from modules targeting different framework versions, or when trying to reference members that may already exist in the target module (e.g., when dealing with ``NullableAttribute`` annotated metadata).
+To account for these situations, the cloner allows for specifying custom reference importer instances. By deriving from the ``CloneContextAwareReferenceImporter`` class and overriding methods such as ``ImportMethod``, we can reroute specific member references to the appropriate metadata if needed. Below is an example of a basic implementation of an importer that attempts to map method references from the ``System.Runtime.CompilerServices`` namespace to definitions that are already present in the target module:
-Injecting the cloned members
+.. code-block:: csharp
+
+ public class MyImporter : CloneContextAwareReferenceImporter
+ {
+ private static readonly SignatureComparer Comparer = new();
+
+ public MyImporter(MemberCloneContext context)
+ : base(context)
+ {
+ }
+
+ public override IMethodDefOrRef ImportMethod(IMethodDefOrRef method)
+ {
+ // Check if the method is from a type defined in the System.Runtime.CompilerServices namespace.
+ if (method.DeclaringType is { Namespace.Value: "System.Runtime.CompilerServices" } type)
+ {
+ // We might already have a type and method defined in the target module (e.g., NullableAttribute::.ctor(int32)).
+ // Try find it in the target module.
+
+ var existingMethod = this.Context.Module
+ .TopLevelTypes.FirstOrDefault(t => t.IsTypeOf(type.Namespace, type.Name))?
+ .Methods.FirstOrDefault(m => method.Name == m.Name && Comparer.Equals(m.Signature, method.Signature));
+
+ // If we found a matching definition, then return it instead of importing the reference.
+ if (existingMethod is not null)
+ return existingMethod;
+ }
+
+ return base.ImportMethod(method);
+ }
+ }
+
+
+We can then pass a custom importer factory to our member cloner constructor as follows:
+
+.. code-block:: csharp
+
+ var cloner = new MemberCloner(destinationModule, context => new MyImporter(context));
+
+
+All references to methods defined in the ``System.Runtime.CompilerServices`` namespace will then be mapped to the appropriate method definitions if they exist in the target module.
+
+See :ref:`dotnet-importer-common-caveats` for more information on reference importing and its caveats.
+
+
+Post processing of cloned members
+---------------------------------
+
+In some cases, cloned members may need to be post-processed before they are injected into the target module. The ``MemberCloner`` class can be initialized with an instance of a ``IMemberClonerListener``, that gets notified by the cloner object every time a definition was cloned.
+
+Below an example that appends the string ``_Cloned`` to the name for every cloned type.
+
+.. code-block:: csharp
+
+ public class MyListener : MemberClonerListener
+ {
+ public override void OnClonedType(TypeDefinition original, TypeDefinition cloned)
+ {
+ cloned.Name = $"{original.Name}_Cloned";
+ base.OnClonedType(original, cloned);
+ }
+ }
+
+
+We can then initialize our cloner with an instance of our listener class:
+
+.. code-block:: csharp
+
+ var cloner = new MemberCloner(destinationModule, new MyListener());
+
+
+Alternatively, we can also override the more generic ``OnClonedMember`` instead, which gets fired for every member definition that was cloned.
+
+.. code-block:: csharp
+
+ public class MyListener : MemberClonerListener
+ {
+ public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned)
+ {
+ /* ... Do post processing here ... */
+ base.OnClonedMember(original, cloned);
+ }
+ }
+
+
+As a shortcut, this can also be done by passing in a delegate or lambda instead to the ``MemberCloner`` constructor.
+
+.. code-block:: csharp
+
+ var cloner = new MemberCloner(destinationModule, (original, cloned) => {
+ /* ... Do post processing here ... */
+ });
+
+
+Injecting the cloned members
----------------------------
-After cloning, we obtain a ``MemberCloneResult``, which contains a register of all members cloned by the member cloner.
+The ``Clone`` method returns a ``MemberCloneResult``, which contains a register of all members cloned by the member cloner.
- ``OriginalMembers``: The collection containing all original members.
- ``ClonedMembers``: The collection containing all cloned members.
@@ -136,9 +243,21 @@ Alternatively, we can get all cloned top-level types.
var clonedTypes = result.ClonedTopLevelTypes;
-It is important to note that the ``MemberCloner`` class itself does not inject any of the cloned members. To inject the cloned types, we can for instance add them to the ``ModuleDefinition.TopLevelTypes`` collection:
+It is important to note that the ``MemberCloner`` class itself does not inject any of the cloned members by itself. To inject the cloned types, we can for instance add them to the ``ModuleDefinition.TopLevelTypes`` collection:
.. code-block:: csharp
foreach (var clonedType in clonedTypes)
- destinationModule.TopLevelTypes.Add(clonedType);
\ No newline at end of file
+ destinationModule.TopLevelTypes.Add(clonedType);
+
+
+However, since injecting the cloned top level types is a very common use-case for the cloner, AsmResolver defines the ``InjectTypeClonerListener`` class that implements a cloner listener that injects all top level types automatically into the destination module. In such a case, the code can be reduced to the following:
+
+.. code-block:: csharp
+
+ new MemberCloner(destinationModule, new InjectTypeClonerListener(destinationModule))
+ .Include(rectangleType)
+ .Include(vectorType)
+ .Clone();
+
+ // `destinationModule` now contains copies of `rectangleType` and `vectorType`.
diff --git a/docs/dotnet/dynamic-methods.rst b/docs/dotnet/dynamic-methods.rst
new file mode 100644
index 000000000..9b43317e7
--- /dev/null
+++ b/docs/dotnet/dynamic-methods.rst
@@ -0,0 +1,63 @@
+Dynamic Methods
+===============
+
+Dynamic methods are methods that are constructed and assembled at run time. They allow for dynamically generating managed code, without having to go through the process of compiling or generating new assemblies. This is used a lot in obfuscators that implement for example reference proxies or virtual machines.
+
+AsmResolver has support for reading dynamic methods, and transforming them into ``MethodDefinition`` objects that can be processed further. All relevant classes are present in the following namespace:
+
+.. code-block:: csharp
+
+ using AsmResolver.DotNet.Dynamic;
+
+.. note::
+
+ Since AsmResolver 5.0, this namespace exists in a separate ``AsmResolver.DotNet.Dynamic`` nuget package.
+
+
+Reading dynamic methods
+-----------------------
+
+The following example demonstrates how to transform an instance of ``DynamicMethod`` into a ``DynamicMethodDefinition``:
+
+.. code-block:: csharp
+
+ DynamicMethod dynamicMethod = ...
+
+ var contextModule = ModuleDefinition.FromFile(typeof(Program).Assembly.Location);
+ var definition = new DynamicMethodDefinition(contextModule, dynamicMethod);
+
+
+Note that the constructor requires a context module. This is the module that will be used to import or resolve any references within the method.
+
+.. warning::
+
+ Reading dynamic methods relies on dynamic analysis, and may therefore result in arbitrary code execution. Make sure to only use this in a safe environment if the input module is not trusted.
+
+
+Using dynamic methods
+---------------------
+
+An instance of ``DynamicMethodDefinition`` is virtually the same as any other ``MethodDefinition``, and thus all its properties can be inspected and modified. Below an example that prints all the instructions that were present in the body of the dynamic method:
+
+.. code-block:: csharp
+
+ DynamicMethodDefinition definition = ...
+ foreach (var instr in definition.CilMethodBody.Instructions)
+ Console.WriteLine(instr);
+
+``DynamicMethodDefinition`` are fully imported method definitions. This means we can safely add them to the context module:
+
+.. code-block:: csharp
+
+ // Read dynamic method.
+ var contextModule = ModuleDefinition.FromFile(typeof(Program).Assembly.Location);
+ var definition = new DynamicMethodDefinition(contextModule, dynamicMethod);
+
+ // Add to type.
+ contextModule.GetOrCreateModuleType().Methods.Add(definition);
+
+ // Save
+ contextModule.Write("Program.patched.dll");
+
+
+See :ref:`dotnet-obtaining-methods-and-fields` and :ref:`dotnet-cil-method-bodies` for more information on how to use ``MethodDefinition`` objects.
diff --git a/docs/dotnet/managed-method-bodies.rst b/docs/dotnet/managed-method-bodies.rst
index 281be6f31..3d438d3de 100644
--- a/docs/dotnet/managed-method-bodies.rst
+++ b/docs/dotnet/managed-method-bodies.rst
@@ -1,3 +1,5 @@
+.. _dotnet-cil-method-bodies:
+
CIL Method Bodies
=================
diff --git a/docs/dotnet/member-tree.rst b/docs/dotnet/member-tree.rst
index bffe00935..633d16808 100644
--- a/docs/dotnet/member-tree.rst
+++ b/docs/dotnet/member-tree.rst
@@ -27,7 +27,7 @@ Below, an example program that iterates through all types recursively and prints
.. code-block:: csharp
public const int IndentationWidth = 3;
-
+
private static void Main(string[] args)
{
var module = ModuleDefinition.FromFile(...);
@@ -41,14 +41,15 @@ Below, an example program that iterates through all types recursively and prints
{
// Print the name of the current type.
Console.WriteLine("{0}- {1} : {2:X8}", indentation, type.Name, type.MetadataToken.ToInt32());
-
+
// Dump any nested types.
DumpTypes(type.NestedTypes, indentationLevel + 1);
}
}
+.. _dotnet-obtaining-methods-and-fields:
-Obtaining methods and fields
+Obtaining methods and fields
----------------------------
The ``TypeDefinition`` class exposes collections of methods and fields that the type defines:
@@ -76,7 +77,7 @@ Methods and fields have a ``Signature`` property, that contain the return and pa
.. code-block:: csharp
FieldDefinition field = ...
- Console.WriteLine("Return type: " + field.Signature.FieldType);
+ Console.WriteLine("Field type: " + field.Signature.FieldType);
However, for reading parameters from a method definition, it is preferred to use the ``Parameters`` property instead of the ``ParameterTypes`` property stored in the signature. This is because the ``Parameters`` property automatically binds the types to the parameter definitions that are associated to these parameter types. This provides additional information, such as the name of the parameter:
@@ -98,7 +99,7 @@ Obtaining properties and events is similar to obtaining methods and fields; ``Ty
Console.WriteLine("{0} : {1:X8}", @event.Name, @event.MetadataToken.ToInt32());
.. code-block:: csharp
-
+
foreach (var property in type.Properties)
Console.WriteLine("{0} : {1:X8}", property.Name, property.MetadataToken.ToInt32());
@@ -112,4 +113,3 @@ Properties and events have methods associated to them. These are accessible thro
Console.WriteLine("{0} {1} : {2:X8}", semantic.Attributes, semantic.Method.Name,
semantic.MetadataToken.ToInt32());
}
-
diff --git a/docs/dotnet/unmanaged-method-bodies.rst b/docs/dotnet/unmanaged-method-bodies.rst
index c5edc20d3..2e733ac28 100644
--- a/docs/dotnet/unmanaged-method-bodies.rst
+++ b/docs/dotnet/unmanaged-method-bodies.rst
@@ -23,23 +23,23 @@ To make the CLR treat the output file as a mixed mode application, the ``ILOnly`
.. code-block:: csharp
ModuleDefinition module = ...
- module.Attributes &= ~DotNetDirectoryFlags.ILOnly;
+ module.IsILOnly = false;
Furthermore, make sure the right architecture is specified. For example, for an x86 64-bit binary, use the following:
.. code-block:: csharp
- module.PEKind = OptionalHeaderMagic.Pe32Plus;
+ module.PEKind = OptionalHeaderMagic.PE32Plus;
module.MachineType = MachineType.Amd64;
- module.Attributes &= ~DotNetDirectoryFlags.Bit32Required;
+ module.IsBit32Required = false;
For 32-bit x86 binaries, use the following:
.. code-block:: csharp
- module.PEKind = OptionalHeaderMagic.Pe32;
+ module.PEKind = OptionalHeaderMagic.PE32;
module.MachineType = MachineType.I386;
- module.Attributes |= DotNetDirectoryFlags.Bit32Required;
+ module.IsBit32Required = true;
Flags for native methods
diff --git a/docs/index.rst b/docs/index.rst
index 89f3b0aeb..da9a805c7 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -58,6 +58,7 @@ Table of Contents:
dotnet/methods
dotnet/managed-method-bodies
dotnet/unmanaged-method-bodies
+ dotnet/dynamic-methods
dotnet/managed-resources
dotnet/cloning
dotnet/token-allocation
diff --git a/docs/pefile/headers.rst b/docs/pefile/headers.rst
index 18468f5b6..fc011d58d 100644
--- a/docs/pefile/headers.rst
+++ b/docs/pefile/headers.rst
@@ -7,6 +7,6 @@ After you obtained an instance of the ``PEFile`` class, it is possible to read a
Console.WriteLine("e_flanew: {0:X8}", peFile.DosHeader.NextHeaderOffset);
Console.WriteLine("Machine: {0:X8}", peFile.FileHeader.Machine);
- Console.WriteLine("Entrypoint: {0:X8}", peFile.OptionalHeader.AddressOfEntrypoint);
+ Console.WriteLine("EntryPoint: {0:X8}", peFile.OptionalHeader.AddressOfEntryPoint);
Every change made to these headers will be reflected in the output executable, however very little verification on these values is done.
diff --git a/docs/peimage/dotnet.rst b/docs/peimage/dotnet.rst
index 73e9175ac..a4f41da3b 100644
--- a/docs/peimage/dotnet.rst
+++ b/docs/peimage/dotnet.rst
@@ -12,7 +12,7 @@ The .NET data directory can be accessed by the ``IPEImage.DotNetDirectory`` prop
IPEImage peImage = ...
- Console.WriteLine("Managed entrypoint: {0:X8}", peImage.DotNetDirectory.Entrypoint);
+ Console.WriteLine("Managed entry point: {0:X8}", peImage.DotNetDirectory.EntryPoint);
Metadata directory
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/.gitignore b/src/.gitignore
new file mode 100644
index 000000000..bebbea9fa
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,2 @@
+!AsmResolver.Symbols.Pdb/
+
diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj
new file mode 100644
index 000000000..5519f663e
--- /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.Dynamic.xml
+
+
+
+ bin\Release\AsmResolver.DotNet.Dynamic.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 90%
rename from src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs
rename to src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs
index d3244f8e2..3efbe83d1 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
@@ -25,7 +26,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe
{
_tokens = tokens ?? throw new ArgumentNullException(nameof(tokens));
_readerContext = contextModule.ReaderContext;
- _importer = new ReferenceImporter(contextModule);
+ _importer = contextModule.DefaultImporter;
}
///
@@ -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 = new BinaryStreamReader((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..e39ebc5f5
--- /dev/null
+++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs
@@ -0,0 +1,116 @@
+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 = module.DefaultImporter.ImportMethodSignature(ResolveSig(methodBase, module));
+ CilMethodBody = CreateDynamicMethodBody(this, dynamicMethodObj);
+ }
+
+ private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition module)
+ {
+ var importer = module.DefaultImporter;
+ var returnType = methodBase is MethodInfo info
+ ? importer.ImportTypeSignature(info.ReturnType)
+ : module.CorLibTypeFactory.Void;
+
+ var parameters = methodBase.GetParameters();
+
+ var parameterTypes = new TypeSignature[parameters.Length];
+ for (int i = 0; i < parameterTypes.Length; i++)
+ parameterTypes[i] = importer.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 not SerializedModuleDefinition module)
+ throw new ArgumentException("Method body should reference a serialized module.");
+
+ var result = new CilMethodBody(method);
+ dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj);
+
+ // Attempt to get the code field.
+ byte[]? code = FieldReader.ReadField(dynamicMethodObj, "m_code");
+
+ // If it is still null, it might still be set using DynamicILInfo::SetCode.
+ // Find the code stored in the DynamicILInfo if available.
+ if (code is null
+ && FieldReader.TryReadField(dynamicMethodObj, "m_method", out var methodBase)
+ && methodBase is not null
+ && FieldReader.TryReadField(methodBase, "m_DynamicILInfo", out object? dynamicILInfo)
+ && dynamicILInfo is not null)
+ {
+ code = FieldReader.ReadField(dynamicILInfo, "m_code");
+ }
+
+ if (code is null)
+ throw new InvalidOperationException("Dynamic method does not have a CIL code stream.");
+
+ // Get remaining fields.
+ object scope = FieldReader.ReadField