Skip to content

Commit

Permalink
Merge pull request #367 from ds5678/use-error-listener-while-building…
Browse files Browse the repository at this point in the history
…-assemblies

Use IErrorListener while building managed assemblies
  • Loading branch information
Washi1337 authored May 7, 2023
2 parents 7b53f6f + 276f6e6 commit 7ae64f8
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 133 deletions.
79 changes: 70 additions & 9 deletions docs/dotnet/advanced-pe-image-building.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ The easiest way to write a .NET module to the disk is by using the ``Write`` met
This method is essentially a shortcut for invoking the ``ManagedPEImageBuilder`` and ``ManagedPEFileBuilder`` classes, and will completely reconstruct the PE image, serialize it into a PE file and write the PE file to the disk.

While this is easy, and would probably work for most .NET module processing, it does not provide much flexibility. To get more control over the construction of the new PE image, it is therefore not recommended to use a different overload of the ``Write`` method, where we pass on a custom ``IPEFileBuilder``, or a configured ``ManagedPEImageBuilder``:
While this is easy, and would probably work for most .NET module processing, it does not provide much flexibility.
To get more control over the construction of the new PE image, it is therefore not recommended to use a different overload of the ``Write`` method that takes instances of ``IPEImageBuilder`` instead:

.. code-block:: csharp
Expand All @@ -22,7 +23,26 @@ While this is easy, and would probably work for most .NET module processing, it
module.Write(@"C:\Path\To\Output\Binary.exe", imageBuilder);
Alternatively, it is possible to call the ``CreateImage`` method directly. This allows for inspecting all build artifacts, as well as post-processing of the constructed PE image before it is written to the disk.
Alternatively, it is possible to call ``ModuleDefinition::ToPEImage`` to turn the module into a ``PEImage`` first, that can then later be post-processed and transformed into a ``PEFile`` to write it to the disk:

.. code-block:: csharp
var imageBuilder = new ManagedPEImageBuilder();
/* Configuration of imageBuilder here... */
// Construct image.
var image = module.ToPEImage(imageBuilder);
// Write image to the disk.
var fileBuilder = new ManagedPEFileBuilder();
var file = fileBuilder.CreateFile(image);
file.Write(@"C:\Path\To\Output\Binary.exe");
To get even more control, it is possible to call the ``CreateImage`` method from the image builder directly.
This allows for inspecting all build artifacts, as well as post-processing of the constructed PE image before it is written to the disk.

.. code-block:: csharp
Expand All @@ -32,6 +52,10 @@ Alternatively, it is possible to call the ``CreateImage`` method directly. This
// Construct image.
var result = imageBuilder.CreateImage(module);
/* Inspect build result ... */
// Obtain constructed PE image.
var image = result.ConstructedImage;
/* Post processing of image happens here... */
Expand Down Expand Up @@ -162,6 +186,13 @@ Below an example on how to preserve maximum stack depths for all methods in the
ComputeMaxStackOnBuildOverride = false
}
.. warning::
Disabling max stack computation may have unexpected side-effects (such as rendering certain CIL method bodies invalid).
Strong name signing
-------------------
Expand Down Expand Up @@ -194,26 +225,56 @@ After writing the module to an output stream, use the ``StrongNameSigner`` class
Image Builder Diagnostics
-------------------------

.NET modules that contain invalid metadata and/or method bodies might cause problems upon serializing it to a PE image or file. To inspect all errors that occurred during the construction of a PE image, call the ``CreateImage`` method directly and get the value of the ``DiagnosticBag`` property. This is a collection that contains all the problems that occurred during the process:
.NET modules that contain invalid metadata and/or method bodies might cause problems upon serializing it to a PE image or file.
To inspect all errors that occurred during the construction of a PE image, call the ``CreateImage`` method with the ``ErrorListener`` property set to an instance of the ``DiagnosticBag`` property.
This is an implementation of ``IErrorListener`` that collects all the problems that occurred during the process:

.. code-block:: csharp
var result = imageBuilder.CreateImage(module);
// Set up a diagnostic bag as an error listener.
var diagnosticBag = new DiagnosticBag();
imageBuilder.ErrorListener = diagnosticBag;
Console.WriteLine("Construction finished with {0} errors.", result.DiagnosticBag.Exceptions.Count);
// Build image.
var result = imageBuilder.CreateImage(module);
// Print all errors.
foreach (var error in result.DiagnosticBag.Exceptions)
Console.WriteLine("Construction finished with {0} errors.", diagnosticBag.Exceptions.Count);
foreach (var error in diagnosticBag.Exceptions)
Console.WriteLine(error.Message);
Whenever a problem is reported, AsmResolver attempts to recover or fill in default data where corrupted data was encountered. To test whether any of the errors resulted in AsmResolver to abort the construction of the image, use the ``IsFatal`` property. If this property is set to ``false``, the image stored in the ``ConstructedImage`` property can be written to the disk:
Whenever a problem is reported, AsmResolver attempts to recover or fill in default data where corrupted data was encountered.
To simply build the PE image ignoring all diagnostic errors, it is also possible to pass in ``EmptyErrorListener.Instance`` instead:

.. code-block:: csharp
if (!result.DiagnosticBag.IsFatal)
imageBuilder.ErrorListener = EmptyErrorListener.Instance;
.. warning::

Using ``EmptyErrorListener`` will surpress any non-critical builder errors, however these errors are typically indicative of an invalid executable being constructed.
Therefore, even if an output file is produced, it may have unexpected side-effects (such as the file not functioning properly).


.. note::

Setting an instance of ``IErrorListener`` in the image builder will only affect the building process.
If the input module is initialized from a file containing invalid metadata, you may still experience reader errors, even if an ``EmptyErrorListener`` is specified.
See :ref:`dotnet-advanced-module-reading` for handling reader diagnostics.


To test whether any of the errors resulted in AsmResolver to abort the construction of the image, use the ``PEImageBuildResult::HasFailed`` property.
If this property is set to ``false``, the image stored in the ``ConstructedImage`` property can be written to the disk:

.. code-block:: csharp
if (!result.HasFailed)
{
var fileBuilder = new ManagedPEFileBuilder();
var file = fileBuilder.CreateFile(result.ConstructedImage);
file.Write("output.exe");
}
}
8 changes: 4 additions & 4 deletions src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ public StrongNamePrivateKey? StrongNamePrivateKey
public virtual DotNetDirectoryBuildResult CreateDotNetDirectory(
ModuleDefinition module,
INativeSymbolsProvider symbolsProvider,
DiagnosticBag diagnosticBag)
IErrorListener errorListener)
{
// Find all members in the module.
var discoveryResult = DiscoverMemberDefinitionsInModule(module);

// Creat new .NET dir buffer.
var buffer = CreateDotNetDirectoryBuffer(module, symbolsProvider, diagnosticBag);
var buffer = CreateDotNetDirectoryBuffer(module, symbolsProvider, errorListener);
buffer.DefineModule(module);

// When specified, import existing AssemblyRef, ModuleRef, TypeRef and MemberRef prior to adding any other
Expand Down Expand Up @@ -164,10 +164,10 @@ private MemberDiscoveryResult DiscoverMemberDefinitionsInModule(ModuleDefinition
private DotNetDirectoryBuffer CreateDotNetDirectoryBuffer(
ModuleDefinition module,
INativeSymbolsProvider symbolsProvider,
DiagnosticBag diagnosticBag)
IErrorListener errorListener)
{
var metadataBuffer = CreateMetadataBuffer(module);
return new DotNetDirectoryBuffer(module, MethodBodySerializer, symbolsProvider, metadataBuffer, diagnosticBag);
return new DotNetDirectoryBuffer(module, MethodBodySerializer, symbolsProvider, metadataBuffer, errorListener);
}

private IMetadataBuffer CreateMetadataBuffer(ModuleDefinition module)
Expand Down
6 changes: 3 additions & 3 deletions src/AsmResolver.DotNet/Builder/IDotNetDirectoryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public interface IDotNetDirectoryFactory
/// </summary>
/// <param name="module">The module to serialize to a .NET data directory.</param>
/// <param name="symbolsProvider">The object responsible for providing references to native symbols.</param>
/// <param name="diagnosticBag">The bag that is used to collect all diagnostic information during the building process. </param>
/// <param name="errorListener">The listener that is used to collect all diagnostic information during the building process. </param>
/// <returns>The serialized data directory.</returns>
/// <exception cref="MetadataBuilderException">Occurs when the metadata builder encounters an error.</exception>
DotNetDirectoryBuildResult CreateDotNetDirectory(ModuleDefinition module, INativeSymbolsProvider symbolsProvider, DiagnosticBag diagnosticBag);
DotNetDirectoryBuildResult CreateDotNetDirectory(ModuleDefinition module, INativeSymbolsProvider symbolsProvider, IErrorListener errorListener);
}
}
}
44 changes: 36 additions & 8 deletions src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using AsmResolver.DotNet.Code.Native;
using AsmResolver.PE;
Expand Down Expand Up @@ -31,11 +31,30 @@ public ManagedPEImageBuilder(MetadataBuilderFlags metadataBuilderFlags)

/// <summary>
/// Creates a new instance of the <see cref="ManagedPEImageBuilder"/> class, using the provided
/// .NET data directory flags.
/// .NET data directory factory.
/// </summary>
public ManagedPEImageBuilder(IDotNetDirectoryFactory factory)
: this(factory, new DiagnosticBag())
{
DotNetDirectoryFactory = factory ?? throw new ArgumentNullException(nameof(factory));
}

/// <summary>
/// Creates a new instance of the <see cref="ManagedPEImageBuilder"/> class, using the provided
/// .NET data directory factory.
/// </summary>
public ManagedPEImageBuilder(IErrorListener errorListener)
: this(new DotNetDirectoryFactory(), errorListener)
{
}

/// <summary>
/// Creates a new instance of the <see cref="ManagedPEImageBuilder"/> class, using the provided
/// .NET data directory factory and error listener.
/// </summary>
public ManagedPEImageBuilder(IDotNetDirectoryFactory factory, IErrorListener errorListener)
{
DotNetDirectoryFactory = factory;
ErrorListener = errorListener;
}

/// <summary>
Expand All @@ -47,10 +66,19 @@ public IDotNetDirectoryFactory DotNetDirectoryFactory
set;
}

/// <summary>
/// Gets or sets the object responsible for keeping track of diagnostics during the building process.
/// </summary>
public IErrorListener ErrorListener
{
get;
set;
}

/// <inheritdoc />
public PEImageBuildResult CreateImage(ModuleDefinition module)
{
var context = new PEImageBuildContext();
var context = new PEImageBuildContext(ErrorListener);

PEImage? image = null;
ITokenMapping? tokenMapping = null;
Expand All @@ -74,7 +102,7 @@ public PEImageBuildResult CreateImage(ModuleDefinition module)
var result = DotNetDirectoryFactory.CreateDotNetDirectory(
module,
symbolProvider,
context.DiagnosticBag);
context.ErrorListener);
image.DotNetDirectory = result.Directory;
tokenMapping = result.TokenMapping;

Expand Down Expand Up @@ -108,12 +136,12 @@ public PEImageBuildResult CreateImage(ModuleDefinition module)
}
catch (Exception ex)
{
context.DiagnosticBag.RegisterException(ex);
context.DiagnosticBag.MarkAsFatal();
context.ErrorListener.RegisterException(ex);
context.ErrorListener.MarkAsFatal();
}

tokenMapping ??= new TokenMapping();
return new PEImageBuildResult(image, context.DiagnosticBag, tokenMapping);
return new PEImageBuildResult(image, context.ErrorListener, tokenMapping);
}
}
}
85 changes: 50 additions & 35 deletions src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,50 @@
using System;

namespace AsmResolver.DotNet.Builder
{
/// <summary>
/// Provides a context in which a PE image construction takes place in.
/// </summary>
public class PEImageBuildContext
{
/// <summary>
/// Creates a new empty build context.
/// </summary>
public PEImageBuildContext()
{
DiagnosticBag = new DiagnosticBag();
}

/// <summary>
/// Creates a new build context.
/// </summary>
/// <param name="diagnosticBag">The diagnostic bag to use.</param>
public PEImageBuildContext(DiagnosticBag diagnosticBag)
{
DiagnosticBag = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag));
}

/// <summary>
/// Gets the bag that collects all diagnostic information during the building process.
/// </summary>
public DiagnosticBag DiagnosticBag
{
get;
}
}
}
using System;

namespace AsmResolver.DotNet.Builder
{
/// <summary>
/// Provides a context in which a PE image construction takes place in.
/// </summary>
public class PEImageBuildContext
{
/// <summary>
/// Creates a new empty build context.
/// </summary>
public PEImageBuildContext()
{
ErrorListener = new DiagnosticBag();
}

/// <summary>
/// Creates a new build context.
/// </summary>
/// <param name="diagnosticBag">The diagnostic bag to use.</param>
public PEImageBuildContext(DiagnosticBag diagnosticBag)
{
ErrorListener = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag));
}

/// <summary>
/// Creates a new build context.
/// </summary>
/// <param name="errorListener">The diagnostic bag to use.</param>
public PEImageBuildContext(IErrorListener errorListener)
{
ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener));
}

/// <summary>
/// Gets the bag that collects all diagnostic information during the building process.
/// </summary>
[Obsolete("Use ErrorListener instead.")]
public DiagnosticBag? DiagnosticBag => ErrorListener as DiagnosticBag;

/// <summary>
/// Gets the error listener that handles all diagnostic information during the building process.
/// </summary>
public IErrorListener ErrorListener
{
get;
}
}
}
Loading

0 comments on commit 7ae64f8

Please sign in to comment.