Skip to content

Commit

Permalink
Merge pull request #564 from Washi1337/feature/empty-error-listener-o…
Browse files Browse the repository at this point in the history
…n-read

Use `EmptyErrorListener` by default on reading files
  • Loading branch information
Washi1337 authored Jun 13, 2024
2 parents 3f78f84 + 19a2c30 commit d781876
Show file tree
Hide file tree
Showing 105 changed files with 1,098 additions and 1,031 deletions.
8 changes: 4 additions & 4 deletions docs/guides/dotnet/advanced-module-reading.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Advanced Module Reading

Advanced users might need to configure AsmResolver\'s module reader. For
example, instead of letting the module reader throw exceptions upon
reading invalid data, errors should be ignored and recovered from. Other
example, instead of letting the module reader ignore exceptions upon
reading invalid data, errors could be collected or thrown. Other
uses might include changing the way the underlying PE or method bodies
are read. These kinds of settings can be configured using the
`ModuleReaderParameters` class.
Expand Down Expand Up @@ -37,14 +37,14 @@ For example, this can be in particular useful if you want to let
AsmResolver ignore and recover from invalid data in the input file:

``` csharp
parameters.PEReaderParameters.ErrorListener = EmptyErrorListener.Instance;
parameters.PEReaderParameters.ErrorListener = ThrowErrorListener.Instance;
```

Alternatively, this property can also be set through the constructor of
the `ModuleReaderParameters` class directly:

``` csharp
var parameters = new ModuleReaderParameters(EmptyErrorListener.Instance);
var parameters = new ModuleReaderParameters(ThrowErrorListener.Instance);
```

For more information on customizing the underlying PE image reading
Expand Down
6 changes: 6 additions & 0 deletions docs/guides/dotnet/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ IntPtr hInstance = Marshal.GetHINSTANCE(module);
var module = ModuleDefinition.FromModuleBaseAddress(hInstance);
```

For more information on customizing the reading process, see [Advanced Module Reading](advanced-module-reading.md).


## Writing a .NET module

Writing a .NET module can be done through one of the `Write` method
Expand Down Expand Up @@ -153,6 +156,9 @@ using var service = new MemoryMappedFileService();
var assembly = AssemblyDefinition.FromFile(service.OpenFile(@"C:\myfile.exe"));
```

For more information on customizing the reading process, see [Advanced Module Reading](advanced-module-reading.md).


## Writing a .NET assembly

Writing a .NET assembly can be done through one of the `Write` method
Expand Down
146 changes: 70 additions & 76 deletions docs/guides/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,95 +2,89 @@

## Why are there so many libraries / packages instead of just one?

Not everyone will need everything from the code base of AsmResolver. For
example, someone that is only interested in reading the PE headers of an
input file does not require any of the functionality of the
`AsmResolver.DotNet` package.

This is why AsmResolver\'s design philosophy is not to be one monolithic
library, but rather be a toolsuite of libraries. By splitting up in
multiple smaller libraries, the user can carefully select the packages
that they need and leave out what they do not need. This can easily
shave off 100s of kilobytes from the total size of code that is shipped.

## Why does AsmResolver throw so many errors on reading/writing?

AsmResolver does verification of the input file, and if it finds
anything that is out of place or not according to specification, it will
report this to the `IErrorListener` passed onto the reader parameters. A
similar thing happens when serializing the input application back to the
disk. By default, this translates to an exception being thrown (e.g. you
might have seen a `System.AggregateException` being thrown upon
writing).

AsmResolver often can ignore and recover from kinds of errors, but this
is not enabled by default. To enable these, please refer to
[Advanced PE Image Reading](peimage/advanced-pe-reading.md#custom-error-handling) (PE)
or [Advanced Module Reading](dotnet/advanced-module-reading.md#pe-image-reading-parameters) (.NET),
and [Image Builder Diagnostics](dotnet/advanced-pe-image-building.md#image-builder-diagnostics) (.NET).
Be careful with ignoring errors though. Especially for disabling writer
verification can cause the output to not work anymore unless you know
what you are doing.

If it still breaks and you believe it is a bug, please report it on the
Not everyone will need everything from the code base of AsmResolver.
An application only interested in the headers of a PE file does not require any of the functionality of the `AsmResolver.DotNet` package.

For this reason, AsmResolver is not a monolithic library but a collection of smaller packages.
This way, you can select the packages that you need and leave out what you do not intend to use.
This can reduce the total deployment size significantly.

If you do not wish to deploy multiple DLLs next to your application, consider deploying your application as a [single-file bundle](https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview?tabs=cli) or use a solution such as [ILRepack](https://github.com/gluck/il-repack) or [ILMerge](https://github.com/dotnet/ILMerge).


## How do I customize reader/writer error handling in AsmResolver?

By default, AsmResolver tries to ignore and recover from invalid data present in the input file, and throws exceptions when attempting to construct and write executable files with invalid data.
This ensures files produced by AsmResolver are by default conforming to standards that the underlying operating system and/or the CLR accepts, and that you do not make any mistakes in constructing invalid executable files.

AsmResolver can be configured to be more strict or more lax in this verification process.
See one of the following articles:
- [Advanced PE Image Reading - Custom Error Handling](peimage/advanced-pe-reading.md#custom-error-handling) (PE)
- [Advanced Module Reading - PE Image Reading Parameters](dotnet/advanced-module-reading.md#pe-image-reading-parameters) (.NET modules),
- [Advanced PE Image Building - Image Builder Diagnostics](dotnet/advanced-pe-image-building.md#image-builder-diagnostics) (.NET modules).

> [!WARNING]
> Be careful with suppressing PE construction errors.
> Disabling verification can cause the output to not work anymore unless you know what you are doing.
If AsmResolver throws an exception that you think should not be thrown, please consider reporting an issue on the
[issues board](https://github.com/Washi1337/AsmResolver/issues).


## Why does the executable not work anymore after modifying it with AsmResolver?

A couple of things can be happening here:

- AsmResolver´s PE builder has a bug.
- You are changing something in the executable you are not supposed to
change.
- You are changing something that results in the executable not
function anymore.
- The target binary is actively trying to prevent you from applying
any modifications (this happens a lot with obfuscated binaries).

With great power comes great responsibility. Changing the wrong things
in the input executable file can result in the output stop working.

For .NET applications, make sure your application conforms with
specification
([ECMA-335](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf)).
To help you with finding problems in your final output, try reopening
the executable in AsmResolver and look for errors reported by the
reader. Alternatively, using tools such as `peverify` for .NET Framework
applications (usually located in
`C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX X.X Tools.`)
or `dotnet ILVerify` for .NET Core / .NET 5+ applications, can also help
narrowing down what might have gone wrong.

If you believe it is a bug in AsmResolver, please report it on the
[issues board](https://github.com/Washi1337/AsmResolver/issues).
One of the following may have happened:

- AsmResolver has a bug.
- You are changing something in the executable you are not supposed to change.
- You are changing something that results in the executable not function anymore.
- The target binary is actively trying to prevent you from applying any modifications (this happens a lot with obfuscated binaries).

## In Mono.Cecil / dnlib my code works, but it does not work in AsmResolver, why?
With great power comes great responsibility.
Changing the wrong things in the input executable file can result in the output stop working.

For .NET applications, make sure your application conforms with specification ([ECMA-335](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf)).
To help you with finding problems in your final output, try reopening the executable in AsmResolver and look for errors reported by the reader.
Alternatively, use the PE verification tools that ships with .NET's SDK:
- `peverify` for .NET Framework (accessible via Visual Studio Developer's command prompt).
- `dotnet ILVerify` for .NET Core / .NET 5+ applications (installable via `dotnet tool install -g dotnet-ilverify `)

If you believe it is a bug in AsmResolver, please report it on the [issues board](https://github.com/Washi1337/AsmResolver/issues).


## My code works using Mono.Cecil/dnlib but not in AsmResolver, why?

Essentially, two things can be happening here:

- AsmResolver´s code could have a bug.
- You are misusing AsmResolver´s API.
- AsmResolver has a bug.
- You are misusing AsmResolver.

It is important to remember that, while the public API of `AsmResolver.DotNet` resembles the ones from Mono.Cecil and dnlib, AsmResolver follows a very different design philosophy.
As such, a lot of classes in those libraries will not map one-to-one to AsmResolver classes directly.
Carefully read exceptions thrown and/or check the documentation to make sure you are not misusing the API.

It is important to remember that, while the public API of
`AsmResolver.DotNet` looks similar on face value to other libraries
(such as Mono.Cecil or dnlib), AsmResolver itself follows a very
different design philosophy than these libraries. As such, a lot of
classes in those libraries will not map one-to-one to AsmResolver
classes directly. Check the documentation to make sure you are not
misusing the API.
If you believe it is a bug, please report it on the [issues board](https://github.com/Washi1337/AsmResolver/issues).

If you believe it is a bug, please report it on the [issues
board](https://github.com/Washi1337/AsmResolver/issues).

## Does AsmResolver have a concept similar to writer events in dnlib?
## Does AsmResolver use writer events (similar to dnlib)?

No.

Instead, to have more control over how the final output executable file
will look like, AsmResolver works in layers of abstraction. For example,
you can manually serialize a `ModuleDefinition` to a `PEImage` first,
before writing it to the disk. This class exposes more low level
structures of the executable file, which can all be changed before
writing to the disk.
Instead, to have more control over how the final output executable file will look like, AsmResolver works in layers of abstraction.
For example, you can manually serialize a `ModuleDefinition` to a `PEImage` first, before writing it to the disk:

```csharp
ModuleDefinition inputModule = ...;

// Construct a PE image from the module.
var newImage = inputModule.ToPEImage();

/* ... Make any additional changes to the image ... */

// Construct a file and write to the disk.
var newFile = newImage.ToPEFile(new ManagedPEFileBuilder());
newFile.Write("output.exe");
```


For more details, refer to [Advanced PE Image Building](dotnet/advanced-pe-image-building.md)
4 changes: 2 additions & 2 deletions docs/guides/peimage/advanced-pe-reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ var image = PEImage.FromFile(@"C:\Path\To\File.exe", parameters);

## Custom error handling

By default, AsmResolver throws exceptions upon encountering invalid data
in the input file. To provide a custom method for handling parser
By default, AsmResolver tries to ignore and recover from encountering invalid
data in the input file. To provide a custom method for handling parser
errors, set the `ErrorListener` property. There are a couple of default
implementations that AsmResolver provides.

Expand Down
3 changes: 2 additions & 1 deletion src/AsmResolver.DotNet/AssemblyDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public static AssemblyDefinition FromBytes(byte[] buffer, ModuleReaderParameters
/// <param name="filePath">The file path to the input executable to load.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromFile(string filePath) => FromFile(filePath, new ModuleReaderParameters(Path.GetDirectoryName(filePath)));
public static AssemblyDefinition FromFile(string filePath)
=> FromFile(filePath, new ModuleReaderParameters(Path.GetDirectoryName(filePath)));

/// <summary>
/// Reads a .NET assembly from the provided input file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public ModuleReaderParameters(IErrorListener errorListener)
/// </summary>
/// <param name="workingDirectory">The working directory of the modules to read.</param>
public ModuleReaderParameters(string? workingDirectory)
: this(workingDirectory, ThrowErrorListener.Instance)
: this(workingDirectory, EmptyErrorListener.Instance)
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/AsmResolver.PE/PEReaderParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class PEReaderParameters
/// Initializes the PE reader parameters.
/// </summary>
public PEReaderParameters()
: this(ThrowErrorListener.Instance)
: this(EmptyErrorListener.Instance)
{
}

Expand Down
44 changes: 38 additions & 6 deletions src/AsmResolver.Symbols.Pdb/PdbImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,36 +127,70 @@ public IList<PdbModule> Modules
/// <returns>The read PDB image.</returns>
public static PdbImage FromFile(string path) => FromFile(MsfFile.FromFile(path));

/// <summary>
/// Reads a PDB image from the provided input file.
/// </summary>
/// <param name="path">The path to the PDB file.</param>
/// <param name="readerParameters">The parameters to use while reading the PDB image.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromFile(string path, PdbReaderParameters readerParameters)
=> FromFile(MsfFile.FromFile(path), readerParameters);

/// <summary>
/// Reads a PDB image from the provided input file.
/// </summary>
/// <param name="file">The input file.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromFile(IInputFile file) => FromFile(MsfFile.FromFile(file));

/// <summary>
/// Reads a PDB image from the provided input file.
/// </summary>
/// <param name="file">The input file.</param>
/// <param name="readerParameters">The parameters to use while reading the PDB image.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromFile(IInputFile file, PdbReaderParameters readerParameters)
=> FromFile(MsfFile.FromFile(file), readerParameters);

/// <summary>
/// Interprets a byte array as a PDB image.
/// </summary>
/// <param name="data">The data to interpret.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromBytes(byte[] data) => FromFile(MsfFile.FromBytes(data));

/// <summary>
/// Interprets a byte array as a PDB image.
/// </summary>
/// <param name="data">The data to interpret.</param>
/// <param name="readerParameters">The parameters to use while reading the PDB image.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromBytes(byte[] data, PdbReaderParameters readerParameters)
=> FromFile(MsfFile.FromBytes(data), readerParameters);

/// <summary>
/// Reads an PDB image from the provided input stream reader.
/// </summary>
/// <param name="reader">The reader.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromReader(BinaryStreamReader reader) => FromFile(MsfFile.FromReader(reader));

/// <summary>
/// Reads an PDB image from the provided input stream reader.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="readerParameters">The parameters to use while reading the PDB image.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromReader(BinaryStreamReader reader, PdbReaderParameters readerParameters)
=> FromFile(MsfFile.FromReader(reader), readerParameters);

/// <summary>
/// Loads a PDB image from the provided MSF file.
/// </summary>
/// <param name="file">The MSF file.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromFile(MsfFile file)
{
return FromFile(file, new PdbReaderParameters(ThrowErrorListener.Instance));
}
=> FromFile(file, new PdbReaderParameters(ThrowErrorListener.Instance));

/// <summary>
/// Loads a PDB image from the provided MSF file.
Expand All @@ -165,9 +199,7 @@ public static PdbImage FromFile(MsfFile file)
/// <param name="readerParameters">The parameters to use while reading the PDB image.</param>
/// <returns>The read PDB image.</returns>
public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParameters)
{
return new SerializedPdbImage(file, readerParameters);
}
=> new SerializedPdbImage(file, readerParameters);

/// <summary>
/// Obtains all records stored in the original TPI stream of the PDB image.
Expand Down
2 changes: 1 addition & 1 deletion src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class PdbReaderParameters
/// Creates new PDB reader parameters.
/// </summary>
public PdbReaderParameters()
: this(ThrowErrorListener.Instance)
: this(EmptyErrorListener.Instance)
{
}

Expand Down
14 changes: 14 additions & 0 deletions test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,18 @@
<ProjectReference Include="..\TestBinaries\DotNet\AsmResolver.DotNet.TestCases.Types\AsmResolver.DotNet.TestCases.Types.csproj" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>
Loading

0 comments on commit d781876

Please sign in to comment.