From 15c3bab951f1190a6647b3d12a6f2e83e0269995 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 20 Apr 2023 18:17:40 +0200 Subject: [PATCH 01/26] Add LazyVariable --- src/AsmResolver.DotNet/AssemblyDefinition.cs | 8 +- src/AsmResolver.DotNet/AssemblyDescriptor.cs | 16 ++-- src/AsmResolver.DotNet/AssemblyReference.cs | 16 ++-- src/AsmResolver.DotNet/ClassLayout.cs | 8 +- src/AsmResolver.DotNet/Constant.cs | 16 ++-- src/AsmResolver.DotNet/CustomAttribute.cs | 24 +++--- src/AsmResolver.DotNet/EventDefinition.cs | 24 +++--- src/AsmResolver.DotNet/ExportedType.cs | 24 +++--- src/AsmResolver.DotNet/FieldDefinition.cs | 72 ++++++++--------- src/AsmResolver.DotNet/FileReference.cs | 16 ++-- src/AsmResolver.DotNet/GenericParameter.cs | 16 ++-- .../GenericParameterConstraint.cs | 16 ++-- src/AsmResolver.DotNet/ImplementationMap.cs | 24 +++--- .../InterfaceImplementation.cs | 16 ++-- src/AsmResolver.DotNet/ManifestResource.cs | 24 +++--- src/AsmResolver.DotNet/MemberReference.cs | 24 +++--- src/AsmResolver.DotNet/MethodDefinition.cs | 64 +++++++-------- src/AsmResolver.DotNet/MethodSemantics.cs | 16 ++-- src/AsmResolver.DotNet/MethodSpecification.cs | 16 ++-- src/AsmResolver.DotNet/ModuleDefinition.cs | 56 ++++++------- src/AsmResolver.DotNet/ModuleReference.cs | 8 +- src/AsmResolver.DotNet/ParameterDefinition.cs | 32 ++++---- src/AsmResolver.DotNet/PropertyDefinition.cs | 32 ++++---- src/AsmResolver.DotNet/SecurityDeclaration.cs | 16 ++-- src/AsmResolver.DotNet/StandAloneSignature.cs | 8 +- src/AsmResolver.DotNet/TypeDefinition.cs | 40 +++++----- src/AsmResolver.DotNet/TypeReference.cs | 28 +++---- src/AsmResolver.DotNet/TypeSpecification.cs | 8 +- src/AsmResolver/LazyVariable.cs | 79 +++++++++++++++++-- 29 files changed, 406 insertions(+), 341 deletions(-) diff --git a/src/AsmResolver.DotNet/AssemblyDefinition.cs b/src/AsmResolver.DotNet/AssemblyDefinition.cs index e379cf590..e0b488f06 100644 --- a/src/AsmResolver.DotNet/AssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/AssemblyDefinition.cs @@ -24,7 +24,7 @@ public class AssemblyDefinition : AssemblyDescriptor, IModuleProvider, IHasSecur { private IList? _modules; private IList? _securityDeclarations; - private readonly LazyVariable _publicKey; + private readonly LazyVariable _publicKey; private byte[]? _publicKeyToken; /// @@ -98,7 +98,7 @@ public static AssemblyDefinition FromImage(IPEImage peImage, ModuleReaderParamet protected AssemblyDefinition(MetadataToken token) : base(token) { - _publicKey = new LazyVariable(GetPublicKey); + _publicKey = new LazyVariable(x => x.GetPublicKey()); } /// @@ -169,10 +169,10 @@ public IList SecurityDeclarations /// public byte[]? PublicKey { - get => _publicKey.Value; + get => _publicKey.GetValue(this); set { - _publicKey.Value = value; + _publicKey.SetValue(value); _publicKeyToken = null; } } diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs index ed22fb622..2447823a7 100644 --- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs @@ -19,8 +19,8 @@ public abstract class AssemblyDescriptor : MetadataMember, IHasCustomAttribute, { private const int PublicKeyTokenLength = 8; - private readonly LazyVariable _name; - private readonly LazyVariable _culture; + private readonly LazyVariable _name; + private readonly LazyVariable _culture; private IList? _customAttributes; /// @@ -30,8 +30,8 @@ public abstract class AssemblyDescriptor : MetadataMember, IHasCustomAttribute, protected AssemblyDescriptor(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _culture = new LazyVariable(GetCulture); + _name = new LazyVariable(x => x.GetName()); + _culture = new LazyVariable(x => x.GetCulture()); Version = new Version(0, 0, 0, 0); } @@ -43,8 +43,8 @@ protected AssemblyDescriptor(MetadataToken token) /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -175,8 +175,8 @@ protected virtual IList GetCustomAttributes() => /// public Utf8String? Culture { - get => _culture.Value; - set => _culture.Value = value; + get => _culture.GetValue(this); + set => _culture.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/AssemblyReference.cs b/src/AsmResolver.DotNet/AssemblyReference.cs index d4432896c..308e46655 100644 --- a/src/AsmResolver.DotNet/AssemblyReference.cs +++ b/src/AsmResolver.DotNet/AssemblyReference.cs @@ -15,8 +15,8 @@ public class AssemblyReference : IOwnedCollectionElement, IImplementation { - private readonly LazyVariable _publicKeyOrToken; - private readonly LazyVariable _hashValue; + private readonly LazyVariable _publicKeyOrToken; + private readonly LazyVariable _hashValue; private byte[]? _publicKeyToken; /// @@ -26,8 +26,8 @@ public class AssemblyReference : protected AssemblyReference(MetadataToken token) : base(token) { - _publicKeyOrToken = new LazyVariable(GetPublicKeyOrToken); - _hashValue = new LazyVariable(GetHashValue); + _publicKeyOrToken = new LazyVariable(x => x.GetPublicKeyOrToken()); + _hashValue = new LazyVariable(x => x.GetHashValue()); } /// @@ -107,8 +107,8 @@ public ModuleDefinition? Module /// public byte[]? PublicKeyOrToken { - get => _publicKeyOrToken.Value; - set => _publicKeyOrToken.Value = value; + get => _publicKeyOrToken.GetValue(this); + set => _publicKeyOrToken.SetValue(value); } /// @@ -116,8 +116,8 @@ public byte[]? PublicKeyOrToken /// public byte[]? HashValue { - get => _hashValue.Value; - set => _hashValue.Value = value; + get => _hashValue.GetValue(this); + set => _hashValue.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/ClassLayout.cs b/src/AsmResolver.DotNet/ClassLayout.cs index d550dac7a..8c38f54ae 100644 --- a/src/AsmResolver.DotNet/ClassLayout.cs +++ b/src/AsmResolver.DotNet/ClassLayout.cs @@ -7,7 +7,7 @@ namespace AsmResolver.DotNet /// public class ClassLayout : MetadataMember { - private readonly LazyVariable _parent; + private readonly LazyVariable _parent; /// /// Initializes the class layout with a metadata token. @@ -16,7 +16,7 @@ public class ClassLayout : MetadataMember protected ClassLayout(MetadataToken token) : base(token) { - _parent = new LazyVariable(GetParent); + _parent = new LazyVariable(x => x.GetParent()); } /// @@ -58,8 +58,8 @@ public uint ClassSize /// public TypeDefinition? Parent { - get => _parent.Value; - internal set => _parent.Value = value; + get => _parent.GetValue(this); + internal set => _parent.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/Constant.cs b/src/AsmResolver.DotNet/Constant.cs index adb4789a7..0448b5386 100644 --- a/src/AsmResolver.DotNet/Constant.cs +++ b/src/AsmResolver.DotNet/Constant.cs @@ -9,8 +9,8 @@ namespace AsmResolver.DotNet /// public class Constant : MetadataMember { - private readonly LazyVariable _parent; - private readonly LazyVariable _value; + private readonly LazyVariable _parent; + private readonly LazyVariable _value; /// /// Initializes the constant with a metadata token. @@ -19,8 +19,8 @@ public class Constant : MetadataMember protected Constant(MetadataToken token) : base(token) { - _parent = new LazyVariable(GetParent); - _value = new LazyVariable(GetValue); + _parent = new LazyVariable(x => x.GetParent()); + _value = new LazyVariable(x => x.GetValue()); } /// @@ -50,8 +50,8 @@ public ElementType Type /// public IHasConstant? Parent { - get => _parent.Value; - internal set => _parent.Value = value; + get => _parent.GetValue(this); + internal set => _parent.SetValue(value); } /// @@ -59,8 +59,8 @@ public IHasConstant? Parent /// public DataBlobSignature? Value { - get => _value.Value; - set => _value.Value = value; + get => _value.GetValue(this); + set => _value.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/CustomAttribute.cs b/src/AsmResolver.DotNet/CustomAttribute.cs index ddfc5d11e..119ec86ab 100644 --- a/src/AsmResolver.DotNet/CustomAttribute.cs +++ b/src/AsmResolver.DotNet/CustomAttribute.cs @@ -9,9 +9,9 @@ namespace AsmResolver.DotNet /// public class CustomAttribute : MetadataMember, IOwnedCollectionElement { - private readonly LazyVariable _parent; - private readonly LazyVariable _constructor; - private readonly LazyVariable _signature; + private readonly LazyVariable _parent; + private readonly LazyVariable _constructor; + private readonly LazyVariable _signature; /// /// Initializes an empty custom attribute. @@ -20,9 +20,9 @@ public class CustomAttribute : MetadataMember, IOwnedCollectionElement(GetParent); - _constructor = new LazyVariable(GetConstructor); - _signature = new LazyVariable(GetSignature); + _parent = new LazyVariable(x => x.GetParent()); + _constructor = new LazyVariable(x => x.GetConstructor()); + _signature = new LazyVariable(x => x.GetSignature()); } /// @@ -53,8 +53,8 @@ public CustomAttribute(ICustomAttributeType? constructor, CustomAttributeSignatu /// public IHasCustomAttribute? Parent { - get => _parent.Value; - private set => _parent.Value = value; + get => _parent.GetValue(this); + private set => _parent.SetValue(value); } IHasCustomAttribute? IOwnedCollectionElement.Owner @@ -68,8 +68,8 @@ public IHasCustomAttribute? Parent /// public ICustomAttributeType? Constructor { - get => _constructor.Value; - set => _constructor.Value = value; + get => _constructor.GetValue(this); + set => _constructor.SetValue(value); } /// @@ -77,8 +77,8 @@ public ICustomAttributeType? Constructor /// public CustomAttributeSignature? Signature { - get => _signature.Value; - set => _signature.Value = value; + get => _signature.GetValue(this); + set => _signature.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/EventDefinition.cs b/src/AsmResolver.DotNet/EventDefinition.cs index cc36a78d7..0e5f7d13f 100644 --- a/src/AsmResolver.DotNet/EventDefinition.cs +++ b/src/AsmResolver.DotNet/EventDefinition.cs @@ -18,9 +18,9 @@ public class EventDefinition : IHasCustomAttribute, IOwnedCollectionElement { - private readonly LazyVariable _name; - private readonly LazyVariable _declaringType; - private readonly LazyVariable _eventType; + private readonly LazyVariable _name; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _eventType; private IList? _semantics; private IList? _customAttributes; @@ -31,9 +31,9 @@ public class EventDefinition : protected EventDefinition(MetadataToken token) : base(token) { - _name = new LazyVariable(() => GetName()); - _eventType = new LazyVariable(GetEventType); - _declaringType = new LazyVariable(GetDeclaringType); + _name = new LazyVariable(x => x.GetName()); + _eventType = new LazyVariable(x => x.GetEventType()); + _declaringType = new LazyVariable(x => x.GetDeclaringType()); } /// @@ -87,8 +87,8 @@ public bool IsRuntimeSpecialName /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -101,8 +101,8 @@ public Utf8String? Name /// public ITypeDefOrRef? EventType { - get => _eventType.Value; - set => _eventType.Value = value; + get => _eventType.GetValue(this); + set => _eventType.SetValue(value); } /// @@ -113,8 +113,8 @@ public ITypeDefOrRef? EventType /// public TypeDefinition? DeclaringType { - get => _declaringType.Value; - private set => _declaringType.Value = value; + get => _declaringType.GetValue(this); + private set => _declaringType.SetValue(value); } ITypeDescriptor? IMemberDescriptor.DeclaringType => DeclaringType; diff --git a/src/AsmResolver.DotNet/ExportedType.cs b/src/AsmResolver.DotNet/ExportedType.cs index c6253d0ba..dce1c0767 100644 --- a/src/AsmResolver.DotNet/ExportedType.cs +++ b/src/AsmResolver.DotNet/ExportedType.cs @@ -16,9 +16,9 @@ public class ExportedType : ITypeDescriptor, IOwnedCollectionElement { - private readonly LazyVariable _name; - private readonly LazyVariable _namespace; - private readonly LazyVariable _implementation; + private readonly LazyVariable _name; + private readonly LazyVariable _namespace; + private readonly LazyVariable _implementation; private IList? _customAttributes; /// @@ -28,9 +28,9 @@ public class ExportedType : protected ExportedType(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _namespace = new LazyVariable(GetNamespace); - _implementation = new LazyVariable(GetImplementation); + _name = new LazyVariable(x => x.GetName()); + _namespace = new LazyVariable(x => x.GetNamespace()); + _implementation = new LazyVariable(x => x.GetImplementation()); } /// @@ -73,8 +73,8 @@ public uint TypeDefId /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -87,8 +87,8 @@ public Utf8String? Name /// public Utf8String? Namespace { - get => _namespace.Value; - set => _namespace.Value = value; + get => _namespace.GetValue(this); + set => _namespace.SetValue(value); } string? ITypeDescriptor.Namespace => Namespace; @@ -114,8 +114,8 @@ public ModuleDefinition? Module /// public IImplementation? Implementation { - get => _implementation.Value; - set => _implementation.Value = value; + get => _implementation.GetValue(this); + set => _implementation.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/FieldDefinition.cs b/src/AsmResolver.DotNet/FieldDefinition.cs index 0d464fb01..87152c14b 100644 --- a/src/AsmResolver.DotNet/FieldDefinition.cs +++ b/src/AsmResolver.DotNet/FieldDefinition.cs @@ -23,14 +23,14 @@ public class FieldDefinition : IHasFieldMarshal, IOwnedCollectionElement { - private readonly LazyVariable _name; - private readonly LazyVariable _signature; - private readonly LazyVariable _declaringType; - private readonly LazyVariable _constant; - private readonly LazyVariable _marshalDescriptor; - private readonly LazyVariable _implementationMap; - private readonly LazyVariable _fieldRva; - private readonly LazyVariable _fieldOffset; + private readonly LazyVariable _name; + private readonly LazyVariable _signature; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _constant; + private readonly LazyVariable _marshalDescriptor; + private readonly LazyVariable _implementationMap; + private readonly LazyVariable _fieldRva; + private readonly LazyVariable _fieldOffset; private IList? _customAttributes; @@ -41,14 +41,14 @@ public class FieldDefinition : protected FieldDefinition(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _signature = new LazyVariable(GetSignature); - _declaringType = new LazyVariable(GetDeclaringType); - _constant = new LazyVariable(GetConstant); - _marshalDescriptor = new LazyVariable(GetMarshalDescriptor); - _implementationMap = new LazyVariable(GetImplementationMap); - _fieldRva = new LazyVariable(GetFieldRva); - _fieldOffset = new LazyVariable(GetFieldOffset); + _name = new LazyVariable(x => x.GetName()); + _signature = new LazyVariable(x => x.GetSignature()); + _declaringType = new LazyVariable(x => x.GetDeclaringType()); + _constant = new LazyVariable(x => x.GetConstant()); + _marshalDescriptor = new LazyVariable(x => x.GetMarshalDescriptor()); + _implementationMap = new LazyVariable(x => x.GetImplementationMap()); + _fieldRva = new LazyVariable(x => x.GetFieldRva()); + _fieldOffset = new LazyVariable(x => x.GetFieldOffset()); } /// @@ -89,8 +89,8 @@ public FieldDefinition(Utf8String name, FieldAttributes attributes, TypeSignatur /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -100,8 +100,8 @@ public Utf8String? Name /// public FieldSignature? Signature { - get => _signature.Value; - set => _signature.Value = value; + get => _signature.GetValue(this); + set => _signature.SetValue(value); } /// @@ -308,8 +308,8 @@ public bool HasFieldRva /// public TypeDefinition? DeclaringType { - get => _declaringType.Value; - private set => _declaringType.Value = value; + get => _declaringType.GetValue(this); + private set => _declaringType.SetValue(value); } TypeDefinition? IOwnedCollectionElement.Owner @@ -334,29 +334,29 @@ public IList CustomAttributes /// public Constant? Constant { - get => _constant.Value; - set => _constant.Value = value; + get => _constant.GetValue(this); + set => _constant.SetValue(value); } /// public MarshalDescriptor? MarshalDescriptor { - get => _marshalDescriptor.Value; - set => _marshalDescriptor.Value = value; + get => _marshalDescriptor.GetValue(this); + set => _marshalDescriptor.SetValue(value); } /// public ImplementationMap? ImplementationMap { - get => _implementationMap.Value; + get => _implementationMap.GetValue(this); set { - if (value?.MemberForwarded is {}) + if (value?.MemberForwarded is not null) throw new ArgumentException("Cannot add an implementation map that was already added to another member."); - if (_implementationMap.Value is {}) - _implementationMap.Value.MemberForwarded = null; - _implementationMap.Value = value; - if (value is {}) + if (_implementationMap.GetValue(this) is { } map) + map.MemberForwarded = null; + _implementationMap.SetValue(value); + if (value is not null) value.MemberForwarded = this; } } @@ -371,8 +371,8 @@ public ImplementationMap? ImplementationMap /// public ISegment? FieldRva { - get => _fieldRva.Value; - set => _fieldRva.Value = value; + get => _fieldRva.GetValue(this); + set => _fieldRva.SetValue(value); } /// @@ -380,8 +380,8 @@ public ISegment? FieldRva /// public int? FieldOffset { - get => _fieldOffset.Value; - set => _fieldOffset.Value = value; + get => _fieldOffset.GetValue(this); + set => _fieldOffset.SetValue(value); } FieldDefinition IFieldDescriptor.Resolve() => this; diff --git a/src/AsmResolver.DotNet/FileReference.cs b/src/AsmResolver.DotNet/FileReference.cs index 48401e611..d8650d029 100644 --- a/src/AsmResolver.DotNet/FileReference.cs +++ b/src/AsmResolver.DotNet/FileReference.cs @@ -15,8 +15,8 @@ public class FileReference : IManagedEntryPoint, IOwnedCollectionElement { - private readonly LazyVariable _name; - private readonly LazyVariable _hashValue; + private readonly LazyVariable _name; + private readonly LazyVariable _hashValue; private IList? _customAttributes; /// @@ -26,8 +26,8 @@ public class FileReference : protected FileReference(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _hashValue = new LazyVariable(GetHashValue); + _name = new LazyVariable(x => x.GetName()); + _hashValue = new LazyVariable(x => x.GetHashValue()); } /// @@ -78,8 +78,8 @@ public bool ContainsNoMetadata /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -105,8 +105,8 @@ public ModuleDefinition? Module /// public byte[]? HashValue { - get => _hashValue.Value; - set => _hashValue.Value = value; + get => _hashValue.GetValue(this); + set => _hashValue.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/GenericParameter.cs b/src/AsmResolver.DotNet/GenericParameter.cs index a4021ca53..898dd6a11 100644 --- a/src/AsmResolver.DotNet/GenericParameter.cs +++ b/src/AsmResolver.DotNet/GenericParameter.cs @@ -16,8 +16,8 @@ public class GenericParameter : IModuleProvider, IOwnedCollectionElement { - private readonly LazyVariable _name; - private readonly LazyVariable _owner; + private readonly LazyVariable _name; + private readonly LazyVariable _owner; private IList? _constraints; private IList? _customAttributes; @@ -28,8 +28,8 @@ public class GenericParameter : protected GenericParameter(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _owner = new LazyVariable(GetOwner); + _name = new LazyVariable(x => x.GetName()); + _owner = new LazyVariable(x => x.GetOwner()); } /// @@ -59,8 +59,8 @@ public GenericParameter(Utf8String? name, GenericParameterAttributes attributes) /// public IHasGenericParameters? Owner { - get => _owner.Value; - private set => _owner.Value = value; + get => _owner.GetValue(this); + private set => _owner.SetValue(value); } IHasGenericParameters? IOwnedCollectionElement.Owner @@ -77,8 +77,8 @@ public IHasGenericParameters? Owner /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; diff --git a/src/AsmResolver.DotNet/GenericParameterConstraint.cs b/src/AsmResolver.DotNet/GenericParameterConstraint.cs index a90888bdd..b1ee65d6d 100644 --- a/src/AsmResolver.DotNet/GenericParameterConstraint.cs +++ b/src/AsmResolver.DotNet/GenericParameterConstraint.cs @@ -14,8 +14,8 @@ public class GenericParameterConstraint : IModuleProvider, IOwnedCollectionElement { - private readonly LazyVariable _owner; - private readonly LazyVariable _constraint; + private readonly LazyVariable _owner; + private readonly LazyVariable _constraint; private IList? _customAttributes; /// @@ -25,8 +25,8 @@ public class GenericParameterConstraint : protected GenericParameterConstraint(MetadataToken token) : base(token) { - _owner = new LazyVariable(GetOwner); - _constraint = new LazyVariable(GetConstraint); + _owner = new LazyVariable(x => x.GetOwner()); + _constraint = new LazyVariable(x => x.GetConstraint()); } /// @@ -44,8 +44,8 @@ public GenericParameterConstraint(ITypeDefOrRef? constraint) /// public GenericParameter? Owner { - get => _owner.Value; - private set => _owner.Value = value; + get => _owner.GetValue(this); + private set => _owner.SetValue(value); } /// @@ -60,8 +60,8 @@ public GenericParameter? Owner /// public ITypeDefOrRef? Constraint { - get => _constraint.Value; - set => _constraint.Value = value; + get => _constraint.GetValue(this); + set => _constraint.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/ImplementationMap.cs b/src/AsmResolver.DotNet/ImplementationMap.cs index b846d8f42..1de68d443 100644 --- a/src/AsmResolver.DotNet/ImplementationMap.cs +++ b/src/AsmResolver.DotNet/ImplementationMap.cs @@ -9,9 +9,9 @@ namespace AsmResolver.DotNet /// public class ImplementationMap : MetadataMember, IFullNameProvider { - private readonly LazyVariable _name; - private readonly LazyVariable _scope; - private readonly LazyVariable _memberForwarded; + private readonly LazyVariable _name; + private readonly LazyVariable _scope; + private readonly LazyVariable _memberForwarded; /// /// Initializes the with a metadata token. @@ -20,9 +20,9 @@ public class ImplementationMap : MetadataMember, IFullNameProvider protected ImplementationMap(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _scope = new LazyVariable(GetScope); - _memberForwarded = new LazyVariable(GetMemberForwarded); + _name = new LazyVariable(x => x.GetName()); + _scope = new LazyVariable(x => x.GetScope()); + _memberForwarded = new LazyVariable(x => x.GetMemberForwarded()); } /// @@ -53,8 +53,8 @@ public ImplementationMapAttributes Attributes /// public IMemberForwarded? MemberForwarded { - get => _memberForwarded.Value; - internal set => _memberForwarded.Value = value; + get => _memberForwarded.GetValue(this); + internal set => _memberForwarded.SetValue(value); } /// @@ -65,8 +65,8 @@ public IMemberForwarded? MemberForwarded /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -81,8 +81,8 @@ public Utf8String? Name /// public ModuleReference? Scope { - get => _scope.Value; - set => _scope.Value = value; + get => _scope.GetValue(this); + set => _scope.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/InterfaceImplementation.cs b/src/AsmResolver.DotNet/InterfaceImplementation.cs index 5184cc074..f34351608 100644 --- a/src/AsmResolver.DotNet/InterfaceImplementation.cs +++ b/src/AsmResolver.DotNet/InterfaceImplementation.cs @@ -14,8 +14,8 @@ public class InterfaceImplementation : IOwnedCollectionElement, IHasCustomAttribute { - private readonly LazyVariable _class; - private readonly LazyVariable _interface; + private readonly LazyVariable _class; + private readonly LazyVariable _interface; private IList? _customAttributes; /// @@ -25,8 +25,8 @@ public class InterfaceImplementation : protected InterfaceImplementation(MetadataToken token) : base(token) { - _class = new LazyVariable(GetClass); - _interface = new LazyVariable(GetInterface); + _class = new LazyVariable(x => x.GetClass()); + _interface = new LazyVariable(x => x.GetInterface()); } /// @@ -44,8 +44,8 @@ public InterfaceImplementation(ITypeDefOrRef? interfaceType) /// public TypeDefinition? Class { - get => _class.Value; - private set => _class.Value = value; + get => _class.GetValue(this); + private set => _class.SetValue(value); } /// @@ -60,8 +60,8 @@ public TypeDefinition? Class /// public ITypeDefOrRef? Interface { - get => _interface.Value; - set => _interface.Value = value; + get => _interface.GetValue(this); + set => _interface.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/ManifestResource.cs b/src/AsmResolver.DotNet/ManifestResource.cs index 4034e9142..ad21436e3 100644 --- a/src/AsmResolver.DotNet/ManifestResource.cs +++ b/src/AsmResolver.DotNet/ManifestResource.cs @@ -18,9 +18,9 @@ public class ManifestResource : IHasCustomAttribute, IOwnedCollectionElement { - private readonly LazyVariable _name; - private readonly LazyVariable _implementation; - private readonly LazyVariable _embeddedData; + private readonly LazyVariable _name; + private readonly LazyVariable _implementation; + private readonly LazyVariable _embeddedData; private IList? _customAttributes; /// @@ -30,9 +30,9 @@ public class ManifestResource : protected ManifestResource(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _implementation = new LazyVariable(GetImplementation); - _embeddedData = new LazyVariable(GetEmbeddedDataSegment); + _name = new LazyVariable(x => x.GetName()); + _implementation = new LazyVariable(x => x.GetImplementation()); + _embeddedData = new LazyVariable(x => x.GetEmbeddedDataSegment()); } /// @@ -110,8 +110,8 @@ public bool IsPrivate /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -121,8 +121,8 @@ public Utf8String? Name /// public IImplementation? Implementation { - get => _implementation.Value; - set => _implementation.Value = value; + get => _implementation.GetValue(this); + set => _implementation.SetValue(value); } /// @@ -135,8 +135,8 @@ public IImplementation? Implementation /// public ISegment? EmbeddedDataSegment { - get => _embeddedData.Value; - set => _embeddedData.Value = value; + get => _embeddedData.GetValue(this); + set => _embeddedData.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/MemberReference.cs b/src/AsmResolver.DotNet/MemberReference.cs index 276ebb1a7..af8606b23 100644 --- a/src/AsmResolver.DotNet/MemberReference.cs +++ b/src/AsmResolver.DotNet/MemberReference.cs @@ -13,9 +13,9 @@ namespace AsmResolver.DotNet /// public class MemberReference : MetadataMember, ICustomAttributeType, IFieldDescriptor { - private readonly LazyVariable _parent; - private readonly LazyVariable _name; - private readonly LazyVariable _signature; + private readonly LazyVariable _parent; + private readonly LazyVariable _name; + private readonly LazyVariable _signature; private IList? _customAttributes; /// @@ -25,9 +25,9 @@ public class MemberReference : MetadataMember, ICustomAttributeType, IFieldDescr protected MemberReference(MetadataToken token) : base(token) { - _parent = new LazyVariable(GetParent); - _name = new LazyVariable(GetName); - _signature = new LazyVariable(GetSignature); + _parent = new LazyVariable(x => x.GetParent()); + _name = new LazyVariable(x => x.GetName()); + _signature = new LazyVariable(x => x.GetSignature()); } /// @@ -50,8 +50,8 @@ public MemberReference(IMemberRefParent? parent, Utf8String? name, MemberSignatu /// public IMemberRefParent? Parent { - get => _parent.Value; - set => _parent.Value = value; + get => _parent.GetValue(this); + set => _parent.SetValue(value); } /// @@ -62,8 +62,8 @@ public IMemberRefParent? Parent /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// @@ -77,8 +77,8 @@ public Utf8String? Name /// public CallingConventionSignature? Signature { - get => _signature.Value; - set => _signature.Value = value; + get => _signature.GetValue(this); + set => _signature.SetValue(value); } MethodSignature? IMethodDescriptor.Signature => Signature as MethodSignature; diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs index 2e40249fc..234396cb2 100644 --- a/src/AsmResolver.DotNet/MethodDefinition.cs +++ b/src/AsmResolver.DotNet/MethodDefinition.cs @@ -27,13 +27,13 @@ public class MethodDefinition : IHasSecurityDeclaration, IManagedEntryPoint { - private readonly LazyVariable _name; - private readonly LazyVariable _declaringType; - private readonly LazyVariable _signature; - private readonly LazyVariable _methodBody; - private readonly LazyVariable _implementationMap; - private readonly LazyVariable _semantics; - private readonly LazyVariable _exportInfo; + private readonly LazyVariable _name; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _signature; + private readonly LazyVariable _methodBody; + private readonly LazyVariable _implementationMap; + private readonly LazyVariable _semantics; + private readonly LazyVariable _exportInfo; private IList? _parameterDefinitions; private ParameterCollection? _parameters; private IList? _customAttributes; @@ -47,13 +47,13 @@ public class MethodDefinition : protected MethodDefinition(MetadataToken token) : base(token) { - _name =new LazyVariable(GetName); - _declaringType = new LazyVariable(GetDeclaringType); - _signature = new LazyVariable(GetSignature); - _methodBody = new LazyVariable(GetBody); - _implementationMap = new LazyVariable(GetImplementationMap); - _semantics = new LazyVariable(GetSemantics); - _exportInfo = new LazyVariable(GetExportInfo); + _name = new LazyVariable(x => GetName()); + _declaringType = new LazyVariable(x => GetDeclaringType()); + _signature = new LazyVariable(x => x.GetSignature()); + _methodBody = new LazyVariable(x => x.GetBody()); + _implementationMap = new LazyVariable(x => x.GetImplementationMap()); + _semantics = new LazyVariable(x => x.GetSemantics()); + _exportInfo = new LazyVariable(x => x.GetExportInfo()); } /// @@ -83,8 +83,8 @@ public MethodDefinition(Utf8String? name, MethodAttributes attributes, MethodSig /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -95,8 +95,8 @@ public Utf8String? Name /// public MethodSignature? Signature { - get => _signature.Value; - set => _signature.Value = value; + get => _signature.GetValue(this); + set => _signature.SetValue(value); } /// @@ -483,8 +483,8 @@ public bool NoInlining /// public TypeDefinition? DeclaringType { - get => _declaringType.Value; - set => _declaringType.Value = value; + get => _declaringType.GetValue(this); + set => _declaringType.SetValue(value); } ITypeDescriptor? IMemberDescriptor.DeclaringType => DeclaringType; @@ -546,8 +546,8 @@ public ParameterCollection Parameters /// public MethodBody? MethodBody { - get => _methodBody.Value; - set => _methodBody.Value = value; + get => _methodBody.GetValue(this); + set => _methodBody.SetValue(value); } /// @@ -594,15 +594,15 @@ public NativeMethodBody? NativeMethodBody /// public ImplementationMap? ImplementationMap { - get => _implementationMap.Value; + get => _implementationMap.GetValue(this); set { - if (value?.MemberForwarded is {}) + if (value?.MemberForwarded is not null) throw new ArgumentException("Cannot add an implementation map that was already added to another member."); - if (_implementationMap.Value is {}) - _implementationMap.Value.MemberForwarded = null; - _implementationMap.Value = value; - if (value is {}) + if (_implementationMap.GetValue(this) is { } map) + map.MemberForwarded = null; + _implementationMap.SetValue(value); + if (value is not null) value.MemberForwarded = this; } } @@ -645,8 +645,8 @@ public IList GenericParameters /// public MethodSemantics? Semantics { - get => _semantics.Value; - set => _semantics.Value = value; + get => _semantics.GetValue(this); + set => _semantics.SetValue(value); } /// @@ -685,8 +685,8 @@ public MethodSemantics? Semantics /// public UnmanagedExportInfo? ExportInfo { - get => _exportInfo.Value; - set => _exportInfo.Value = value; + get => _exportInfo.GetValue(this); + set => _exportInfo.SetValue(value); } MethodDefinition IMethodDescriptor.Resolve() => this; diff --git a/src/AsmResolver.DotNet/MethodSemantics.cs b/src/AsmResolver.DotNet/MethodSemantics.cs index 42409c737..4686c6b48 100644 --- a/src/AsmResolver.DotNet/MethodSemantics.cs +++ b/src/AsmResolver.DotNet/MethodSemantics.cs @@ -10,8 +10,8 @@ namespace AsmResolver.DotNet /// public class MethodSemantics : MetadataMember, IOwnedCollectionElement { - private readonly LazyVariable _method; - private readonly LazyVariable _association; + private readonly LazyVariable _method; + private readonly LazyVariable _association; /// /// Initializes an empty method semantics object. @@ -20,8 +20,8 @@ public class MethodSemantics : MetadataMember, IOwnedCollectionElement(GetMethod); - _association = new LazyVariable(GetAssociation); + _method = new LazyVariable(x => x.GetMethod()); + _association = new LazyVariable(x => x.GetAssociation()); } /// @@ -50,8 +50,8 @@ public MethodSemanticsAttributes Attributes /// public MethodDefinition? Method { - get => _method.Value; - private set => _method.Value = value; + get => _method.GetValue(this); + private set => _method.SetValue(value); } /// @@ -59,8 +59,8 @@ public MethodDefinition? Method /// public IHasSemantics? Association { - get => _association.Value; - private set => _association.Value = value; + get => _association.GetValue(this); + private set => _association.SetValue(value); } IHasSemantics? IOwnedCollectionElement.Owner diff --git a/src/AsmResolver.DotNet/MethodSpecification.cs b/src/AsmResolver.DotNet/MethodSpecification.cs index e9b9a3210..a01257405 100644 --- a/src/AsmResolver.DotNet/MethodSpecification.cs +++ b/src/AsmResolver.DotNet/MethodSpecification.cs @@ -11,8 +11,8 @@ namespace AsmResolver.DotNet /// public class MethodSpecification : MetadataMember, IMethodDescriptor, IHasCustomAttribute { - private readonly LazyVariable _method; - private readonly LazyVariable _signature; + private readonly LazyVariable _method; + private readonly LazyVariable _signature; private IList? _customAttributes; /// @@ -22,8 +22,8 @@ public class MethodSpecification : MetadataMember, IMethodDescriptor, IHasCustom protected MethodSpecification(MetadataToken token) : base(token) { - _method = new LazyVariable(GetMethod); - _signature = new LazyVariable(GetSignature); + _method = new LazyVariable(x => x.GetMethod()); + _signature = new LazyVariable(x => x.GetSignature()); } /// @@ -43,8 +43,8 @@ public MethodSpecification(IMethodDefOrRef? method, GenericInstanceMethodSignatu /// public IMethodDefOrRef? Method { - get => _method.Value; - set => _method.Value = value; + get => _method.GetValue(this); + set => _method.SetValue(value); } MethodSignature? IMethodDescriptor.Signature => Method?.Signature; @@ -54,8 +54,8 @@ public IMethodDefOrRef? Method /// public GenericInstanceMethodSignature? Signature { - get => _signature.Value; - set => _signature.Value = value; + get => _signature.GetValue(this); + set => _signature.SetValue(value); } diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 710745aa4..a4108cfc6 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -35,24 +35,24 @@ public class ModuleDefinition : { private static MethodInfo? GetHINSTANCEMethod; - private readonly LazyVariable _name; - private readonly LazyVariable _mvid; - private readonly LazyVariable _encId; - private readonly LazyVariable _encBaseId; + private readonly LazyVariable _name; + private readonly LazyVariable _mvid; + private readonly LazyVariable _encId; + private readonly LazyVariable _encBaseId; private IList? _topLevelTypes; private IList? _assemblyReferences; private IList? _customAttributes; - private readonly LazyVariable _managedEntryPoint; + private readonly LazyVariable _managedEntryPoint; private IList? _moduleReferences; private IList? _fileReferences; private IList? _resources; private IList? _exportedTypes; private TokenAllocator? _tokenAllocator; - private readonly LazyVariable _runtimeVersion; - private readonly LazyVariable _nativeResources; + private readonly LazyVariable _runtimeVersion; + private readonly LazyVariable _nativeResources; private IList? _debugData; private ReferenceImporter? _defaultImporter; @@ -267,13 +267,13 @@ public static ModuleDefinition FromImage(IPEImage peImage, ModuleReaderParameter protected ModuleDefinition(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _mvid = new LazyVariable(GetMvid); - _encId = new LazyVariable(GetEncId); - _encBaseId = new LazyVariable(GetEncBaseId); - _managedEntryPoint = new LazyVariable(GetManagedEntryPoint); - _runtimeVersion = new LazyVariable(GetRuntimeVersion); - _nativeResources = new LazyVariable(GetNativeResources); + _name = new LazyVariable(x => x.GetName()); + _mvid = new LazyVariable(x => x.GetMvid()); + _encId = new LazyVariable(x => x.GetEncId()); + _encBaseId = new LazyVariable(x => x.GetEncBaseId()); + _managedEntryPoint = new LazyVariable(x => x.GetManagedEntryPoint()); + _runtimeVersion = new LazyVariable(x => x.GetRuntimeVersion()); + _nativeResources = new LazyVariable(x => x.GetNativeResources()); Attributes = DotNetDirectoryFlags.ILOnly; } @@ -384,8 +384,8 @@ public AssemblyDefinition? Assembly /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -416,8 +416,8 @@ public ushort Generation /// public Guid Mvid { - get => _mvid.Value; - set => _mvid.Value = value; + get => _mvid.GetValue(this); + set => _mvid.SetValue(value); } /// @@ -428,8 +428,8 @@ public Guid Mvid /// public Guid EncId { - get => _encId.Value; - set => _encId.Value = value; + get => _encId.GetValue(this); + set => _encId.SetValue(value); } /// @@ -440,8 +440,8 @@ public Guid EncId /// public Guid EncBaseId { - get => _encBaseId.Value; - set => _encBaseId.Value = value; + get => _encBaseId.GetValue(this); + set => _encBaseId.SetValue(value); } /// @@ -635,8 +635,8 @@ public IList DebugData /// public string RuntimeVersion { - get => _runtimeVersion.Value; - set => _runtimeVersion.Value = value; + get => _runtimeVersion.GetValue(this); + set => _runtimeVersion.SetValue(value); } /// @@ -645,8 +645,8 @@ public string RuntimeVersion /// public IResourceDirectory? NativeResourceDirectory { - get => _nativeResources.Value; - set => _nativeResources.Value = value; + get => _nativeResources.GetValue(this); + set => _nativeResources.SetValue(value); } /// @@ -772,8 +772,8 @@ public MethodDefinition? ManagedEntryPointMethod /// public IManagedEntryPoint? ManagedEntryPoint { - get => _managedEntryPoint.Value; - set => _managedEntryPoint.Value = value; + get => _managedEntryPoint.GetValue(this); + set => _managedEntryPoint.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/ModuleReference.cs b/src/AsmResolver.DotNet/ModuleReference.cs index 87f8a52e2..f4666024c 100644 --- a/src/AsmResolver.DotNet/ModuleReference.cs +++ b/src/AsmResolver.DotNet/ModuleReference.cs @@ -15,7 +15,7 @@ public class ModuleReference : IHasCustomAttribute, IOwnedCollectionElement { - private readonly LazyVariable _name; + private readonly LazyVariable _name; private IList? _customAttributes; /// @@ -25,7 +25,7 @@ public class ModuleReference : protected ModuleReference(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -46,8 +46,8 @@ public ModuleReference(Utf8String? name) /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; diff --git a/src/AsmResolver.DotNet/ParameterDefinition.cs b/src/AsmResolver.DotNet/ParameterDefinition.cs index 81e815ea9..0327a669d 100644 --- a/src/AsmResolver.DotNet/ParameterDefinition.cs +++ b/src/AsmResolver.DotNet/ParameterDefinition.cs @@ -22,10 +22,10 @@ public class ParameterDefinition : IHasFieldMarshal, IOwnedCollectionElement { - private readonly LazyVariable _name; - private readonly LazyVariable _method; - private readonly LazyVariable _constant; - private readonly LazyVariable _marshalDescriptor; + private readonly LazyVariable _name; + private readonly LazyVariable _method; + private readonly LazyVariable _constant; + private readonly LazyVariable _marshalDescriptor; private IList? _customAttributes; /// @@ -35,10 +35,10 @@ public class ParameterDefinition : protected ParameterDefinition(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _method = new LazyVariable(GetMethod); - _constant = new LazyVariable(GetConstant); - _marshalDescriptor = new LazyVariable(GetMarshalDescriptor); + _name = new LazyVariable(x => x.GetName()); + _method = new LazyVariable(x => x.GetMethod()); + _constant = new LazyVariable(x => x.GetConstant()); + _marshalDescriptor = new LazyVariable(x => x.GetMarshalDescriptor()); } /// @@ -73,8 +73,8 @@ public ParameterDefinition(ushort sequence, Utf8String? name, ParameterAttribute /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -162,8 +162,8 @@ public bool HasFieldMarshal /// public MethodDefinition? Method { - get => _method.Value; - private set => _method.Value = value; + get => _method.GetValue(this); + private set => _method.SetValue(value); } MethodDefinition? IOwnedCollectionElement.Owner @@ -194,8 +194,8 @@ public IList CustomAttributes /// public Constant? Constant { - get => _constant.Value; - set => _constant.Value = value; + get => _constant.GetValue(this); + set => _constant.SetValue(value); } /// @@ -206,8 +206,8 @@ public Constant? Constant /// public MarshalDescriptor? MarshalDescriptor { - get => _marshalDescriptor.Value; - set => _marshalDescriptor.Value = value; + get => _marshalDescriptor.GetValue(this); + set => _marshalDescriptor.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/PropertyDefinition.cs b/src/AsmResolver.DotNet/PropertyDefinition.cs index ac35369ab..db8b9cc79 100644 --- a/src/AsmResolver.DotNet/PropertyDefinition.cs +++ b/src/AsmResolver.DotNet/PropertyDefinition.cs @@ -20,10 +20,10 @@ public class PropertyDefinition : IHasConstant, IOwnedCollectionElement { - private readonly LazyVariable _name; - private readonly LazyVariable _declaringType; - private readonly LazyVariable _signature; - private readonly LazyVariable _constant; + private readonly LazyVariable _name; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _signature; + private readonly LazyVariable _constant; private IList? _semantics; private IList? _customAttributes; @@ -34,10 +34,10 @@ public class PropertyDefinition : protected PropertyDefinition(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _signature = new LazyVariable(GetSignature); - _declaringType = new LazyVariable(GetDeclaringType); - _constant = new LazyVariable(GetConstant); + _name = new LazyVariable(x => x.GetName()); + _signature = new LazyVariable(x => x.GetSignature()); + _declaringType = new LazyVariable(x => x.GetDeclaringType()); + _constant = new LazyVariable(x => x.GetConstant()); } /// @@ -102,8 +102,8 @@ public bool HasDefault /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -117,8 +117,8 @@ public Utf8String? Name /// public PropertySignature? Signature { - get => _signature.Value; - set => _signature.Value = value; + get => _signature.GetValue(this); + set => _signature.SetValue(value); } /// @@ -129,8 +129,8 @@ public PropertySignature? Signature /// public TypeDefinition? DeclaringType { - get => _declaringType.Value; - private set => _declaringType.Value = value; + get => _declaringType.GetValue(this); + private set => _declaringType.SetValue(value); } ITypeDescriptor? IMemberDescriptor.DeclaringType => DeclaringType; @@ -166,8 +166,8 @@ public IList CustomAttributes /// public Constant? Constant { - get => _constant.Value; - set => _constant.Value = value; + get => _constant.GetValue(this); + set => _constant.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/SecurityDeclaration.cs b/src/AsmResolver.DotNet/SecurityDeclaration.cs index 7f3c1d572..5688f8b92 100644 --- a/src/AsmResolver.DotNet/SecurityDeclaration.cs +++ b/src/AsmResolver.DotNet/SecurityDeclaration.cs @@ -12,8 +12,8 @@ public class SecurityDeclaration : MetadataMember, IOwnedCollectionElement { - private readonly LazyVariable _parent; - private readonly LazyVariable _permissionSet; + private readonly LazyVariable _parent; + private readonly LazyVariable _permissionSet; /// /// Initializes the with a metadata token. @@ -22,8 +22,8 @@ public class SecurityDeclaration : protected SecurityDeclaration(MetadataToken token) : base(token) { - _parent = new LazyVariable(GetParent); - _permissionSet = new LazyVariable(GetPermissionSet); + _parent = new LazyVariable(x => x.GetParent()); + _permissionSet = new LazyVariable(x => x.GetPermissionSet()); } /// @@ -52,8 +52,8 @@ public SecurityAction Action /// public IHasSecurityDeclaration? Parent { - get => _parent.Value; - private set => _parent.Value = value; + get => _parent.GetValue(this); + private set => _parent.SetValue(value); } /// @@ -68,8 +68,8 @@ public IHasSecurityDeclaration? Parent /// public PermissionSetSignature? PermissionSet { - get => _permissionSet.Value; - set => _permissionSet.Value = value; + get => _permissionSet.GetValue(this); + set => _permissionSet.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/StandAloneSignature.cs b/src/AsmResolver.DotNet/StandAloneSignature.cs index c6a90ac16..3e6bed4c6 100644 --- a/src/AsmResolver.DotNet/StandAloneSignature.cs +++ b/src/AsmResolver.DotNet/StandAloneSignature.cs @@ -15,7 +15,7 @@ namespace AsmResolver.DotNet /// public class StandAloneSignature : MetadataMember, IHasCustomAttribute { - private readonly LazyVariable _signature; + private readonly LazyVariable _signature; private IList? _customAttributes; /// @@ -25,7 +25,7 @@ public class StandAloneSignature : MetadataMember, IHasCustomAttribute protected StandAloneSignature(MetadataToken token) : base(token) { - _signature = new LazyVariable(GetSignature); + _signature = new LazyVariable(x => x.GetSignature()); } /// @@ -43,8 +43,8 @@ public StandAloneSignature(BlobSignature signature) /// public BlobSignature? Signature { - get => _signature.Value; - set => _signature.Value = value; + get => _signature.GetValue(this); + set => _signature.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index 21821ad1c..b3792ad8b 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -27,11 +27,11 @@ public class TypeDefinition : { internal static readonly Utf8String ModuleTypeName = ""; - private readonly LazyVariable _namespace; - private readonly LazyVariable _name; - private readonly LazyVariable _baseType; - private readonly LazyVariable _declaringType; - private readonly LazyVariable _classLayout; + private readonly LazyVariable _namespace; + private readonly LazyVariable _name; + private readonly LazyVariable _baseType; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _classLayout; private IList? _nestedTypes; private ModuleDefinition? _module; private IList? _fields; @@ -51,11 +51,11 @@ public class TypeDefinition : protected TypeDefinition(MetadataToken token) : base(token) { - _namespace = new LazyVariable(GetNamespace); - _name = new LazyVariable(GetName); - _baseType = new LazyVariable(GetBaseType); - _declaringType = new LazyVariable(GetDeclaringType); - _classLayout = new LazyVariable(GetClassLayout); + _namespace = new LazyVariable(x => x.GetNamespace()); + _name = new LazyVariable(x => x.GetName()); + _baseType = new LazyVariable(x => x.GetBaseType()); + _declaringType = new LazyVariable(x => x.GetDeclaringType()); + _classLayout = new LazyVariable(x => x.GetClassLayout()); } /// @@ -93,8 +93,8 @@ public TypeDefinition(Utf8String? ns, Utf8String? name, TypeAttributes attribute /// public Utf8String? Namespace { - get => _namespace.Value; - set => _namespace.Value = value; + get => _namespace.GetValue(this); + set => _namespace.SetValue(value); } string? ITypeDescriptor.Namespace => Namespace; @@ -107,8 +107,8 @@ public Utf8String? Namespace /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -415,8 +415,8 @@ public bool HasSecurity /// public ITypeDefOrRef? BaseType { - get => _baseType.Value; - set => _baseType.Value = value; + get => _baseType.GetValue(this); + set => _baseType.SetValue(value); } /// @@ -435,8 +435,8 @@ public ITypeDefOrRef? BaseType /// public TypeDefinition? DeclaringType { - get => _declaringType.Value; - private set => _declaringType.Value = value; + get => _declaringType.GetValue(this); + private set => _declaringType.SetValue(value); } ITypeDefOrRef? ITypeDefOrRef.DeclaringType => DeclaringType; @@ -642,8 +642,8 @@ public IList MethodImplementations /// public ClassLayout? ClassLayout { - get => _classLayout.Value; - set => _classLayout.Value = value; + get => _classLayout.GetValue(this); + set => _classLayout.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/TypeReference.cs b/src/AsmResolver.DotNet/TypeReference.cs index 2db296a1a..5e5294d7b 100644 --- a/src/AsmResolver.DotNet/TypeReference.cs +++ b/src/AsmResolver.DotNet/TypeReference.cs @@ -14,9 +14,9 @@ public class TypeReference : ITypeDefOrRef, IResolutionScope { - private readonly LazyVariable _name; - private readonly LazyVariable _namespace; - private readonly LazyVariable _scope; + private readonly LazyVariable _name; + private readonly LazyVariable _namespace; + private readonly LazyVariable _scope; private IList? _customAttributes; /// @@ -26,9 +26,9 @@ public class TypeReference : protected TypeReference(MetadataToken token) : base(token) { - _name = new LazyVariable(GetName); - _namespace = new LazyVariable(GetNamespace); - _scope = new LazyVariable(GetScope); + _name = new LazyVariable(x => x.GetName()); + _namespace = new LazyVariable(x => x.GetNamespace()); + _scope = new LazyVariable(x => x.GetScope()); } /// @@ -40,7 +40,7 @@ protected TypeReference(MetadataToken token) public TypeReference(IResolutionScope? scope, Utf8String? ns, Utf8String? name) : this(new MetadataToken(TableIndex.TypeRef, 0)) { - _scope.Value = scope; + _scope.SetValue(scope); Namespace = ns; Name = name; } @@ -55,7 +55,7 @@ public TypeReference(IResolutionScope? scope, Utf8String? ns, Utf8String? name) public TypeReference(ModuleDefinition? module, IResolutionScope? scope, Utf8String? ns, Utf8String? name) : this(new MetadataToken(TableIndex.TypeRef, 0)) { - _scope.Value = scope; + _scope.SetValue(scope); Module = module; Namespace = ns; Name = name; @@ -69,8 +69,8 @@ public TypeReference(ModuleDefinition? module, IResolutionScope? scope, Utf8Stri /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } string? INameProvider.Name => Name; @@ -83,8 +83,8 @@ public Utf8String? Name /// public Utf8String? Namespace { - get => _namespace.Value; - set => _namespace.Value = value; + get => _namespace.GetValue(this); + set => _namespace.SetValue(value); } string? ITypeDescriptor.Namespace => Namespace; @@ -95,8 +95,8 @@ public Utf8String? Namespace /// public IResolutionScope? Scope { - get => _scope.Value; - set => _scope.Value = value; + get => _scope.GetValue(this); + set => _scope.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/TypeSpecification.cs b/src/AsmResolver.DotNet/TypeSpecification.cs index d601ec7a7..56c0388c8 100644 --- a/src/AsmResolver.DotNet/TypeSpecification.cs +++ b/src/AsmResolver.DotNet/TypeSpecification.cs @@ -14,7 +14,7 @@ public class TypeSpecification : MetadataMember, ITypeDefOrRef { - private readonly LazyVariable _signature; + private readonly LazyVariable _signature; private IList? _customAttributes; /// @@ -24,7 +24,7 @@ public class TypeSpecification : protected TypeSpecification(MetadataToken token) : base(token) { - _signature = new LazyVariable(GetSignature); + _signature = new LazyVariable(x => x.GetSignature()); } /// @@ -42,8 +42,8 @@ public TypeSpecification(TypeSignature? signature) /// public TypeSignature? Signature { - get => _signature.Value; - set => _signature.Value = value; + get => _signature.GetValue(this); + set => _signature.SetValue(value); } /// diff --git a/src/AsmResolver/LazyVariable.cs b/src/AsmResolver/LazyVariable.cs index c0f4775d7..6b4ac9814 100644 --- a/src/AsmResolver/LazyVariable.cs +++ b/src/AsmResolver/LazyVariable.cs @@ -6,21 +6,21 @@ namespace AsmResolver /// /// Represents a variable that can be lazily initialized and/or assigned a new value. /// - /// The type of the values that the variable stores. + /// The type of the values that the variable stores. /// /// For performance reasons, this class locks on itself for thread synchronization. Therefore, consumers /// should not lock instances of this class as a lock object to avoid dead-locks. /// - public class LazyVariable + public sealed class LazyVariable { - private T? _value; - private readonly Func? _getValue; + private TValue? _value; + private readonly Func? _getValue; /// /// Creates a new lazy variable and initialize it with a constant. /// /// The value to initialize the variable with. - public LazyVariable(T value) + public LazyVariable(TValue value) { _value = value; IsInitialized = true; @@ -30,7 +30,7 @@ public LazyVariable(T value) /// Creates a new lazy variable and delays the initialization of the default value. /// /// The method to execute when initializing the default value. - public LazyVariable(Func getValue) + public LazyVariable(Func getValue) { _getValue = getValue ?? throw new ArgumentNullException(nameof(getValue)); } @@ -48,7 +48,7 @@ public bool IsInitialized /// /// Gets or sets the value of the variable. /// - public T Value + public TValue Value { get { @@ -79,4 +79,69 @@ private void InitializeValue() } } + + public sealed class LazyVariable + { + private TValue? _value; + private readonly Func? _getValue; + + /// + /// Creates a new lazy variable and initialize it with a constant. + /// + /// The value to initialize the variable with. + public LazyVariable(TValue value) + { + _value = value; + IsInitialized = true; + } + + /// + /// Creates a new lazy variable and delays the initialization of the default value. + /// + /// The method to execute when initializing the default value. + public LazyVariable(Func getValue) + { + _getValue = getValue ?? throw new ArgumentNullException(nameof(getValue)); + } + + /// + /// Gets a value indicating the value has been initialized. + /// + [MemberNotNullWhen(false, nameof(_getValue))] + public bool IsInitialized + { + get; + private set; + } + + public TValue GetValue(TOwner owner) + { + if (!IsInitialized) + InitializeValue(owner); + return _value!; + } + + public void SetValue(TValue value) + { + lock (this) + { + _value = value; + IsInitialized = true; + } + } + + private void InitializeValue(TOwner owner) + { + lock (this) + { + if (!IsInitialized) + { + _value = _getValue(owner); + IsInitialized = true; + } + } + } + + } + } From 6ac13ad5cf2a11b508cec85d3297911bf4b69922 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 20 Apr 2023 18:32:15 +0200 Subject: [PATCH 02/26] Move PE.File, PE and remainder of DotNet to new lazyvar. --- src/AsmResolver.DotNet/Bundles/BundleFile.cs | 10 ++-- .../Resources/ResourceSetEntry.cs | 14 ++--- src/AsmResolver.PE.File/PEFile.cs | 16 +++--- src/AsmResolver.PE/Debug/DebugDataEntry.cs | 10 ++-- src/AsmResolver.PE/DotNet/DotNetDirectory.cs | 56 +++++++++---------- .../DotNet/Metadata/Tables/TablesStream.cs | 12 ++-- src/AsmResolver.PE/Exports/ExportDirectory.cs | 10 ++-- src/AsmResolver.PE/PEImage.cs | 40 ++++++------- src/AsmResolver.PE/Tls/TlsDirectory.cs | 8 +-- .../Win32Resources/ResourceData.cs | 8 +-- 10 files changed, 92 insertions(+), 92 deletions(-) diff --git a/src/AsmResolver.DotNet/Bundles/BundleFile.cs b/src/AsmResolver.DotNet/Bundles/BundleFile.cs index 958eeba16..4dcffc01c 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFile.cs @@ -11,7 +11,7 @@ namespace AsmResolver.DotNet.Bundles /// public class BundleFile : IOwnedCollectionElement { - private readonly LazyVariable _contents; + private readonly LazyVariable _contents; /// /// Creates a new empty bundle file. @@ -20,7 +20,7 @@ public class BundleFile : IOwnedCollectionElement public BundleFile(string relativePath) { RelativePath = relativePath; - _contents = new LazyVariable(GetContents); + _contents = new LazyVariable(x => x.GetContents()); } /// @@ -44,7 +44,7 @@ public BundleFile(string relativePath, BundleFileType type, ISegment contents) { RelativePath = relativePath; Type = type; - _contents = new LazyVariable(contents); + _contents = new LazyVariable(contents); } /// @@ -102,8 +102,8 @@ public bool IsCompressed /// public ISegment Contents { - get => _contents.Value; - set => _contents.Value = value; + get => _contents.GetValue(this); + set => _contents.SetValue(value); } /// diff --git a/src/AsmResolver.DotNet/Resources/ResourceSetEntry.cs b/src/AsmResolver.DotNet/Resources/ResourceSetEntry.cs index 8dab0efd0..d8be45eee 100644 --- a/src/AsmResolver.DotNet/Resources/ResourceSetEntry.cs +++ b/src/AsmResolver.DotNet/Resources/ResourceSetEntry.cs @@ -8,7 +8,7 @@ namespace AsmResolver.DotNet.Resources /// public class ResourceSetEntry { - private readonly LazyVariable _data; + private readonly LazyVariable _data; /// /// Creates a new empty resource set entry. @@ -19,7 +19,7 @@ public ResourceSetEntry(string name, ResourceTypeCode typeCode) { Name = name; Type = IntrinsicResourceType.Get(typeCode); - _data = new LazyVariable(GetData); + _data = new LazyVariable(x => x.GetData()); } /// @@ -31,7 +31,7 @@ public ResourceSetEntry(string name, ResourceType type) { Name = name; Type = type; - _data = new LazyVariable(GetData); + _data = new LazyVariable(x => x.GetData()); } /// @@ -44,7 +44,7 @@ public ResourceSetEntry(string name, ResourceTypeCode typeCode, object? data) { Name = name; Type = IntrinsicResourceType.Get(typeCode); - _data = new LazyVariable(data); + _data = new LazyVariable(data); } /// @@ -57,7 +57,7 @@ public ResourceSetEntry(string name, ResourceType type, object? data) { Name = name; Type = type; - _data = new LazyVariable(data); + _data = new LazyVariable(data); } /// @@ -81,8 +81,8 @@ public ResourceType Type /// public object? Data { - get => _data.Value; - set => _data.Value = value; + get => _data.GetValue(this); + set => _data.SetValue(value); } /// diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index d1483258b..09d15695b 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -20,8 +20,8 @@ public class PEFile : IPEFile /// public const uint ValidPESignature = 0x4550; // "PE\0\0" - private readonly LazyVariable _extraSectionData; - private readonly LazyVariable _eofData; + private readonly LazyVariable _extraSectionData; + private readonly LazyVariable _eofData; private IList? _sections; /// @@ -43,8 +43,8 @@ public PEFile(DosHeader dosHeader, FileHeader fileHeader, OptionalHeader optiona DosHeader = dosHeader ?? throw new ArgumentNullException(nameof(dosHeader)); FileHeader = fileHeader ?? throw new ArgumentNullException(nameof(fileHeader)); OptionalHeader = optionalHeader ?? throw new ArgumentNullException(nameof(optionalHeader)); - _extraSectionData = new LazyVariable(GetExtraSectionData); - _eofData = new LazyVariable(GetEofData); + _extraSectionData = new LazyVariable(x =>x.GetExtraSectionData()); + _eofData = new LazyVariable(x =>x.GetEofData()); MappingMode = PEMappingMode.Unmapped; } @@ -97,15 +97,15 @@ public PEMappingMode MappingMode /// public ISegment? ExtraSectionData { - get => _extraSectionData.Value; - set => _extraSectionData.Value = value; + get => _extraSectionData.GetValue(this); + set => _extraSectionData.SetValue(value); } /// public ISegment? EofData { - get => _eofData.Value; - set => _eofData.Value = value; + get => _eofData.GetValue(this); + set => _eofData.SetValue(value); } /// diff --git a/src/AsmResolver.PE/Debug/DebugDataEntry.cs b/src/AsmResolver.PE/Debug/DebugDataEntry.cs index 44cd67726..533948854 100644 --- a/src/AsmResolver.PE/Debug/DebugDataEntry.cs +++ b/src/AsmResolver.PE/Debug/DebugDataEntry.cs @@ -21,14 +21,14 @@ public class DebugDataEntry : SegmentBase + sizeof(uint) // PointerToRawData ; - private readonly LazyVariable _contents; + private readonly LazyVariable _contents; /// /// Initializes an empty instance. /// protected DebugDataEntry() { - _contents = new LazyVariable(GetContents); + _contents = new LazyVariable(x => x.GetContents()); } /// @@ -37,7 +37,7 @@ protected DebugDataEntry() /// The contents. public DebugDataEntry(IDebugDataSegment contents) { - _contents = new LazyVariable(contents); + _contents = new LazyVariable(contents); } /// @@ -81,8 +81,8 @@ public ushort MinorVersion /// public IDebugDataSegment? Contents { - get => _contents.Value; - set => _contents.Value = value; + get => _contents.GetValue(this); + set => _contents.SetValue(value); } /// diff --git a/src/AsmResolver.PE/DotNet/DotNetDirectory.cs b/src/AsmResolver.PE/DotNet/DotNetDirectory.cs index 6f4941cc7..f41bd379c 100644 --- a/src/AsmResolver.PE/DotNet/DotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/DotNetDirectory.cs @@ -11,26 +11,26 @@ namespace AsmResolver.PE.DotNet /// public class DotNetDirectory : SegmentBase, IDotNetDirectory { - private readonly LazyVariable _metadata; - private readonly LazyVariable _resources; - private readonly LazyVariable _strongName; - private readonly LazyVariable _codeManagerTable; - private readonly LazyVariable _exportAddressTable; - private readonly LazyVariable _vtableFixups; - private readonly LazyVariable _managedNativeHeader; + private readonly LazyVariable _metadata; + private readonly LazyVariable _resources; + private readonly LazyVariable _strongName; + private readonly LazyVariable _codeManagerTable; + private readonly LazyVariable _exportAddressTable; + private readonly LazyVariable _vtableFixups; + private readonly LazyVariable _managedNativeHeader; /// /// Creates a new .NET data directory. /// public DotNetDirectory() { - _metadata = new LazyVariable(GetMetadata); - _resources = new LazyVariable(GetResources); - _strongName = new LazyVariable(GetStrongName); - _codeManagerTable = new LazyVariable(GetCodeManagerTable); - _exportAddressTable = new LazyVariable(GetExportAddressTable); - _vtableFixups = new LazyVariable(GetVTableFixups); - _managedNativeHeader = new LazyVariable(GetManagedNativeHeader); + _metadata = new LazyVariable(x => x.GetMetadata()); + _resources = new LazyVariable(x => x.GetResources()); + _strongName = new LazyVariable(x => x.GetStrongName()); + _codeManagerTable = new LazyVariable(x => x.GetCodeManagerTable()); + _exportAddressTable = new LazyVariable(x => x.GetExportAddressTable()); + _vtableFixups = new LazyVariable(x => x.GetVTableFixups()); + _managedNativeHeader = new LazyVariable(x => x.GetManagedNativeHeader()); } /// @@ -50,8 +50,8 @@ public ushort MinorRuntimeVersion /// public IMetadata? Metadata { - get => _metadata.Value; - set => _metadata.Value = value; + get => _metadata.GetValue(this); + set => _metadata.SetValue(value); } /// @@ -71,43 +71,43 @@ public DotNetEntryPoint EntryPoint /// public DotNetResourcesDirectory? DotNetResources { - get => _resources.Value; - set => _resources.Value = value; + get => _resources.GetValue(this); + set => _resources.SetValue(value); } /// public IReadableSegment? StrongName { - get => _strongName.Value; - set => _strongName.Value = value; + get => _strongName.GetValue(this); + set => _strongName.SetValue(value); } /// public IReadableSegment? CodeManagerTable { - get => _codeManagerTable.Value; - set => _codeManagerTable.Value = value; + get => _codeManagerTable.GetValue(this); + set => _codeManagerTable.SetValue(value); } /// public VTableFixupsDirectory? VTableFixups { - get => _vtableFixups.Value; - set => _vtableFixups.Value = value; + get => _vtableFixups.GetValue(this); + set => _vtableFixups.SetValue(value); } /// public IReadableSegment? ExportAddressTable { - get => _exportAddressTable.Value; - set => _exportAddressTable.Value = value; + get => _exportAddressTable.GetValue(this); + set => _exportAddressTable.SetValue(value); } /// public IReadableSegment? ManagedNativeHeader { - get => _managedNativeHeader.Value; - set => _managedNativeHeader.Value = value; + get => _managedNativeHeader.GetValue(this); + set => _managedNativeHeader.SetValue(value); } /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index 67efc4806..d12f5ed78 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -33,16 +33,16 @@ public partial class TablesStream : SegmentBase, IMetadataStream public const string UncompressedStreamName = "#Schema"; private readonly Dictionary _indexEncoders; - private readonly LazyVariable> _tables; - private readonly LazyVariable> _layouts; + private readonly LazyVariable> _tables; + private readonly LazyVariable> _layouts; /// /// Creates a new, empty tables stream. /// public TablesStream() { - _layouts = new LazyVariable>(GetTableLayouts); - _tables = new LazyVariable>(GetTables); + _layouts = new LazyVariable>(x => x.GetTableLayouts()); + _tables = new LazyVariable>(x => x.GetTables()); _indexEncoders = CreateIndexEncoders(); } @@ -223,12 +223,12 @@ public uint[]? ExternalRowCounts /// 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.GetValue(this); /// /// Gets the layout of all tables in the stream. /// - protected IList TableLayouts => _layouts.Value; + protected IList TableLayouts => _layouts.GetValue(this); /// public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException(); diff --git a/src/AsmResolver.PE/Exports/ExportDirectory.cs b/src/AsmResolver.PE/Exports/ExportDirectory.cs index 5f983b233..e19649ee1 100644 --- a/src/AsmResolver.PE/Exports/ExportDirectory.cs +++ b/src/AsmResolver.PE/Exports/ExportDirectory.cs @@ -10,7 +10,7 @@ namespace AsmResolver.PE.Exports /// public class ExportDirectory : IExportDirectory { - private readonly LazyVariable _name; + private readonly LazyVariable _name; private IList? _exports; /// @@ -18,7 +18,7 @@ public class ExportDirectory : IExportDirectory /// protected ExportDirectory() { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -27,7 +27,7 @@ protected ExportDirectory() /// The name of the library exporting the symbols. public ExportDirectory(string name) { - _name = new LazyVariable(name ?? throw new ArgumentNullException(nameof(name))); + _name = new LazyVariable(name ?? throw new ArgumentNullException(nameof(name))); } /// @@ -61,8 +61,8 @@ public ushort MinorVersion /// public string? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.PE/PEImage.cs b/src/AsmResolver.PE/PEImage.cs index f1e61fbad..7849e172f 100644 --- a/src/AsmResolver.PE/PEImage.cs +++ b/src/AsmResolver.PE/PEImage.cs @@ -21,13 +21,13 @@ namespace AsmResolver.PE public class PEImage : IPEImage { private IList? _imports; - private readonly LazyVariable _exports; - private readonly LazyVariable _resources; - private readonly LazyVariable _exceptions; + private readonly LazyVariable _exports; + private readonly LazyVariable _resources; + private readonly LazyVariable _exceptions; private IList? _relocations; - private readonly LazyVariable _dotNetDirectory; + private readonly LazyVariable _dotNetDirectory; private IList? _debugData; - private readonly LazyVariable _tlsDirectory; + private readonly LazyVariable _tlsDirectory; /// /// Opens a PE image from a specific file on the disk. @@ -169,11 +169,11 @@ public static IPEImage FromFile(IPEFile peFile, PEReaderParameters readerParamet /// public PEImage() { - _exports = new LazyVariable(GetExports); - _resources = new LazyVariable(GetResources); - _exceptions = new LazyVariable(GetExceptions); - _dotNetDirectory = new LazyVariable(GetDotNetDirectory); - _tlsDirectory = new LazyVariable(GetTlsDirectory); + _exports = new LazyVariable(x => x.GetExports()); + _resources = new LazyVariable(x => x.GetResources()); + _exceptions = new LazyVariable(x => x.GetExceptions()); + _dotNetDirectory = new LazyVariable(x => x.GetDotNetDirectory()); + _tlsDirectory = new LazyVariable(x => x.GetTlsDirectory()); } /// @@ -247,22 +247,22 @@ public IList Imports /// public IExportDirectory? Exports { - get => _exports.Value; - set => _exports.Value = value; + get => _exports.GetValue(this); + set => _exports.SetValue(value); } /// public IResourceDirectory? Resources { - get => _resources.Value; - set => _resources.Value = value; + get => _resources.GetValue(this); + set => _resources.SetValue(value); } /// public IExceptionDirectory? Exceptions { - get => _exceptions.Value; - set => _exceptions.Value = value; + get => _exceptions.GetValue(this); + set => _exceptions.SetValue(value); } /// @@ -279,8 +279,8 @@ public IList Relocations /// public IDotNetDirectory? DotNetDirectory { - get => _dotNetDirectory.Value; - set => _dotNetDirectory.Value = value; + get => _dotNetDirectory.GetValue(this); + set => _dotNetDirectory.SetValue(value); } /// @@ -297,8 +297,8 @@ public IList DebugData /// public ITlsDirectory? TlsDirectory { - get => _tlsDirectory.Value; - set => _tlsDirectory.Value = value; + get => _tlsDirectory.GetValue(this); + set => _tlsDirectory.SetValue(value); } /// diff --git a/src/AsmResolver.PE/Tls/TlsDirectory.cs b/src/AsmResolver.PE/Tls/TlsDirectory.cs index af563f6d7..6f231fd23 100644 --- a/src/AsmResolver.PE/Tls/TlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/TlsDirectory.cs @@ -10,7 +10,7 @@ namespace AsmResolver.PE.Tls /// public class TlsDirectory : SegmentBase, ITlsDirectory { - private readonly LazyVariable _templateData; + private readonly LazyVariable _templateData; private TlsCallbackCollection? _callbackFunctions; private ulong _imageBase = 0x00400000; private bool _is32Bit = true; @@ -20,15 +20,15 @@ public class TlsDirectory : SegmentBase, ITlsDirectory /// public TlsDirectory() { - _templateData = new LazyVariable(GetTemplateData); + _templateData = new LazyVariable(x => x.GetTemplateData()); Index = SegmentReference.Null; } /// public IReadableSegment? TemplateData { - get => _templateData.Value; - set => _templateData.Value = value; + get => _templateData.GetValue(this); + set => _templateData.SetValue(value); } /// diff --git a/src/AsmResolver.PE/Win32Resources/ResourceData.cs b/src/AsmResolver.PE/Win32Resources/ResourceData.cs index 94a9337ca..c6659539d 100644 --- a/src/AsmResolver.PE/Win32Resources/ResourceData.cs +++ b/src/AsmResolver.PE/Win32Resources/ResourceData.cs @@ -9,14 +9,14 @@ namespace AsmResolver.PE.Win32Resources /// public class ResourceData : IResourceData { - private readonly LazyVariable _contents; + private readonly LazyVariable _contents; /// /// Initializes a new resource data entry. /// protected ResourceData() { - _contents = new LazyVariable(GetContents); + _contents = new LazyVariable(x => x.GetContents()); } /// @@ -79,8 +79,8 @@ public uint Id /// public ISegment? Contents { - get => _contents.Value; - set => _contents.Value = value; + get => _contents.GetValue(this); + set => _contents.SetValue(value); } /// From 89f934b129313fe7fd1b3f064adb8bb36eed7500 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 20 Apr 2023 19:03:33 +0200 Subject: [PATCH 03/26] Migrate Pdb to new lazyvar. --- .../Leaves/ArrayTypeRecord.cs | 36 ++++++++--------- .../Leaves/BaseClassField.cs | 10 ++--- .../Leaves/BitFieldTypeRecord.cs | 10 ++--- .../Leaves/ClassTypeRecord.cs | 20 +++++----- .../Leaves/CodeViewCompositeTypeRecord.cs | 16 ++++---- .../Leaves/CodeViewDataField.cs | 10 ++--- .../Leaves/CodeViewDerivedTypeRecord.cs | 8 ++-- .../Leaves/CodeViewNamedField.cs | 8 ++-- .../Leaves/EnumerateField.cs | 10 ++--- .../Leaves/FunctionIdentifier.cs | 20 +++++----- .../Leaves/MemberFunctionLeaf.cs | 40 +++++++++---------- .../Leaves/MethodListEntry.cs | 12 +++--- .../Leaves/ModifierTypeRecord.cs | 10 ++--- .../Leaves/NestedTypeField.cs | 12 +++--- .../Leaves/NonOverloadedMethod.cs | 12 +++--- .../Leaves/OverloadedMethod.cs | 14 +++---- .../Leaves/PointerTypeRecord.cs | 12 +++--- .../Leaves/ProcedureTypeRecord.cs | 20 +++++----- .../Leaves/StringIdentifier.cs | 20 +++++----- .../Leaves/UnionTypeRecord.cs | 10 ++--- .../Leaves/VBaseClassField.cs | 20 +++++----- .../Leaves/VTableField.cs | 10 ++--- .../Metadata/Dbi/DbiStream.cs | 16 ++++---- .../Metadata/Modi/ModiStream.cs | 32 +++++++-------- src/AsmResolver.Symbols.Pdb/PdbModule.cs | 30 +++++++------- .../Records/BasePointerRelativeSymbol.cs | 20 +++++----- .../Records/BuildInfoSymbol.cs | 10 ++--- .../Records/CallSiteSymbol.cs | 10 ++--- .../Records/CoffGroupSymbol.cs | 10 ++--- .../Records/CompileSymbol.cs | 8 ++-- .../Records/ConstantSymbol.cs | 20 +++++----- .../Records/DataSymbol.cs | 20 +++++----- .../Records/FileStaticSymbol.cs | 20 +++++----- .../Records/InlineSiteSymbol.cs | 10 ++--- .../Records/LabelSymbol.cs | 10 ++--- .../Records/LocalSymbol.cs | 20 +++++----- .../Records/ObjectNameSymbol.cs | 10 ++--- .../Records/ProcedureReferenceSymbol.cs | 20 ++++------ .../Records/ProcedureSymbol.cs | 24 +++++------ .../Records/PublicSymbol.cs | 10 ++--- .../Records/RegisterRelativeSymbol.cs | 20 +++++----- .../Records/RegisterSymbol.cs | 20 +++++----- .../Records/SectionSymbol.cs | 10 ++--- .../Records/ThunkSymbol.cs | 10 ++--- .../Records/UserDefinedTypeSymbol.cs | 20 +++++----- .../Records/UsingNamespaceSymbol.cs | 10 ++--- src/AsmResolver/LazyVariable.cs | 21 ++++++++-- 47 files changed, 381 insertions(+), 370 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs index 1f53f2071..61ec5bff5 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs @@ -5,9 +5,9 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class ArrayTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _elementType; - private readonly LazyVariable _indexType; - private readonly LazyVariable _name; + private readonly LazyVariable _elementType; + private readonly LazyVariable _indexType; + private readonly LazyVariable _name; /// /// Initializes a new empty array type. @@ -16,9 +16,9 @@ public class ArrayTypeRecord : CodeViewTypeRecord protected ArrayTypeRecord(uint typeIndex) : base(typeIndex) { - _elementType = new LazyVariable(GetElementType); - _indexType = new LazyVariable(GetIndexType); - _name = new LazyVariable(GetName); + _elementType = new LazyVariable(x => x.GetElementType()); + _indexType = new LazyVariable(x => x.GetIndexType()); + _name = new LazyVariable(x => x.GetName()); } /// @@ -30,10 +30,10 @@ protected ArrayTypeRecord(uint typeIndex) public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length) : base(0) { - _elementType = new LazyVariable(elementType); - _indexType = new LazyVariable(indexType); + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); + _name = new LazyVariable(Utf8String.Empty); Length = length; - _name = new LazyVariable(Utf8String.Empty); } /// @@ -46,10 +46,10 @@ public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexT public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length, Utf8String name) : base(0) { - _elementType = new LazyVariable(elementType); - _indexType = new LazyVariable(indexType); + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); + _name = new LazyVariable(name); Length = length; - _name = new LazyVariable(Name); } /// @@ -60,8 +60,8 @@ public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexT /// public CodeViewTypeRecord? ElementType { - get => _elementType.Value; - set => _elementType.Value = value; + get => _elementType.GetValue(this); + set => _elementType.SetValue(value); } /// @@ -69,8 +69,8 @@ public CodeViewTypeRecord? ElementType /// public CodeViewTypeRecord? IndexType { - get => _indexType.Value; - set => _indexType.Value = value; + get => _indexType.GetValue(this); + set => _indexType.SetValue(value); } /// @@ -87,8 +87,8 @@ public ulong Length /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs index 3a3823945..c47a47fce 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class BaseClassField : CodeViewField { - private readonly LazyVariable _type; + private readonly LazyVariable _type; /// /// Initializes an empty base class. @@ -14,7 +14,7 @@ public class BaseClassField : CodeViewField protected BaseClassField(uint typeIndex) : base(typeIndex) { - _type = new LazyVariable(GetBaseType); + _type = new LazyVariable(x => x.GetBaseType()); } /// @@ -24,7 +24,7 @@ protected BaseClassField(uint typeIndex) public BaseClassField(CodeViewTypeRecord type) : base(0) { - _type = new LazyVariable(type); + _type = new LazyVariable(type); } /// @@ -35,8 +35,8 @@ public BaseClassField(CodeViewTypeRecord type) /// public CodeViewTypeRecord? Type { - get => _type.Value; - set => _type.Value = value; + get => _type.GetValue(this); + set => _type.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs index 40a01fbb5..9c93991a2 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class BitFieldTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _type; + private readonly LazyVariable _type; /// /// Initializes an empty bit field record. @@ -14,7 +14,7 @@ public class BitFieldTypeRecord : CodeViewTypeRecord protected BitFieldTypeRecord(uint typeIndex) : base(typeIndex) { - _type = new LazyVariable(GetBaseType); + _type = new LazyVariable(x => x.GetBaseType()); } /// @@ -26,7 +26,7 @@ protected BitFieldTypeRecord(uint typeIndex) public BitFieldTypeRecord(CodeViewTypeRecord type, byte position, byte length) : base(0) { - _type = new LazyVariable(type); + _type = new LazyVariable(type); Position = position; Length = length; } @@ -39,8 +39,8 @@ public BitFieldTypeRecord(CodeViewTypeRecord type, byte position, byte length) /// public CodeViewTypeRecord? Type { - get => _type.Value; - set => _type.Value = value; + get => _type.GetValue(this); + set => _type.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs index 3f222effc..faf4aecf7 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs @@ -7,8 +7,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class ClassTypeRecord : CodeViewDerivedTypeRecord { - private readonly LazyVariable _uniqueName; - private readonly LazyVariable _vtableShape; + private readonly LazyVariable _uniqueName; + private readonly LazyVariable _vtableShape; /// /// Initializes an empty class type. @@ -25,8 +25,8 @@ protected ClassTypeRecord(CodeViewLeafKind kind, uint typeIndex) throw new ArgumentOutOfRangeException(nameof(kind)); LeafKind = kind; - _uniqueName = new LazyVariable(GetUniqueName); - _vtableShape = new LazyVariable(GetVTableShape); + _uniqueName = new LazyVariable(x => x.GetUniqueName()); + _vtableShape = new LazyVariable(x => x.GetVTableShape()); } /// @@ -50,8 +50,8 @@ public ClassTypeRecord(CodeViewLeafKind kind, Utf8String name, Utf8String unique LeafKind = kind; Name = name; - _uniqueName = new LazyVariable(uniqueName); - _vtableShape = new LazyVariable(default(VTableShapeLeaf)); + _uniqueName = new LazyVariable(uniqueName); + _vtableShape = new LazyVariable(default(VTableShapeLeaf)); Size = size; StructureAttributes = attributes; BaseType = baseType; @@ -77,8 +77,8 @@ public ulong Size /// public Utf8String UniqueName { - get => _uniqueName.Value; - set => _uniqueName.Value = value; + get => _uniqueName.GetValue(this); + set => _uniqueName.SetValue(value); } /// @@ -86,8 +86,8 @@ public Utf8String UniqueName /// public VTableShapeLeaf? VTableShape { - get => _vtableShape.Value; - set => _vtableShape.Value = value; + get => _vtableShape.GetValue(this); + set => _vtableShape.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs index 9abc46710..b71238178 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs @@ -5,8 +5,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public abstract class CodeViewCompositeTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _name; - private readonly LazyVariable _fields; + private readonly LazyVariable _name; + private readonly LazyVariable _fields; /// /// Initializes a new empty composite type. @@ -15,8 +15,8 @@ public abstract class CodeViewCompositeTypeRecord : CodeViewTypeRecord protected CodeViewCompositeTypeRecord(uint typeIndex) : base(typeIndex) { - _name = new LazyVariable(GetName); - _fields = new LazyVariable(GetFields); + _name = new LazyVariable(x => x.GetName()); + _fields = new LazyVariable(x => x.GetFields()); } /// @@ -33,8 +33,8 @@ public StructureAttributes StructureAttributes /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// @@ -42,8 +42,8 @@ public Utf8String Name /// public FieldListLeaf? Fields { - get => _fields.Value; - set => _fields.Value = value; + get => _fields.GetValue(this); + set => _fields.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs index 3821a14fe..64a0edeb9 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public abstract class CodeViewDataField : CodeViewNamedField { - private readonly LazyVariable _dataType; + private readonly LazyVariable _dataType; /// /// Initializes an empty instance data member. @@ -14,7 +14,7 @@ public abstract class CodeViewDataField : CodeViewNamedField protected CodeViewDataField(uint typeIndex) : base(typeIndex) { - _dataType = new LazyVariable(GetDataType); + _dataType = new LazyVariable(x => x.GetDataType()); } /// @@ -25,7 +25,7 @@ protected CodeViewDataField(uint typeIndex) protected CodeViewDataField(CodeViewTypeRecord dataType, Utf8String name) : base(0) { - _dataType = new LazyVariable(dataType); + _dataType = new LazyVariable(dataType); Name = name; } @@ -34,8 +34,8 @@ protected CodeViewDataField(CodeViewTypeRecord dataType, Utf8String name) /// public CodeViewTypeRecord DataType { - get => _dataType.Value; - set => _dataType.Value = value; + get => _dataType.GetValue(this); + set => _dataType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs index 3cd51d0f8..fd4b7f068 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs @@ -5,13 +5,13 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public abstract class CodeViewDerivedTypeRecord : CodeViewCompositeTypeRecord { - private readonly LazyVariable _baseType; + private readonly LazyVariable _baseType; /// protected CodeViewDerivedTypeRecord(uint typeIndex) : base(typeIndex) { - _baseType = new LazyVariable(GetBaseType); + _baseType = new LazyVariable(x => x.GetBaseType()); } /// @@ -19,8 +19,8 @@ protected CodeViewDerivedTypeRecord(uint typeIndex) /// public CodeViewTypeRecord? BaseType { - get => _baseType.Value; - set => _baseType.Value = value; + get => _baseType.GetValue(this); + set => _baseType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs index 3137dabde..1b8e72fe2 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public abstract class CodeViewNamedField : CodeViewField { - private readonly LazyVariable _name; + private readonly LazyVariable _name; /// /// Initializes an empty CodeView field leaf. @@ -14,7 +14,7 @@ public abstract class CodeViewNamedField : CodeViewField protected CodeViewNamedField(uint typeIndex) : base(typeIndex) { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -22,8 +22,8 @@ protected CodeViewNamedField(uint typeIndex) /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs index 4fe1a97c9..27538f17b 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class EnumerateField : CodeViewNamedField { - private readonly LazyVariable _value; + private readonly LazyVariable _value; /// /// Initializes an empty enumerate field leaf. @@ -14,7 +14,7 @@ public class EnumerateField : CodeViewNamedField protected EnumerateField(uint typeIndex) : base(typeIndex) { - _value = new LazyVariable(GetValue); + _value = new LazyVariable(x => x.GetValue()); } /// @@ -27,7 +27,7 @@ public EnumerateField(Utf8String name, object value, CodeViewFieldAttributes att : base(0) { Name = name; - _value = new LazyVariable(value); + _value = new LazyVariable(value); Attributes = attributes; } @@ -39,8 +39,8 @@ public EnumerateField(Utf8String name, object value, CodeViewFieldAttributes att /// public object Value { - get => _value.Value; - set => _value.Value = value; + get => _value.GetValue(this); + set => _value.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FunctionIdentifier.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FunctionIdentifier.cs index 326194b14..3c3dc7ae4 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/FunctionIdentifier.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/FunctionIdentifier.cs @@ -5,8 +5,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class FunctionIdentifier : CodeViewLeaf, IIpiLeaf { - private readonly LazyVariable _name; - private readonly LazyVariable _functionType; + private readonly LazyVariable _name; + private readonly LazyVariable _functionType; /// /// Initializes an empty function identifier leaf. @@ -15,8 +15,8 @@ public class FunctionIdentifier : CodeViewLeaf, IIpiLeaf protected FunctionIdentifier(uint typeIndex) : base(typeIndex) { - _name = new LazyVariable(GetName); - _functionType = new LazyVariable(GetFunctionType); + _name = new LazyVariable(x => x.GetName()); + _functionType = new LazyVariable(x => x.GetFunctionType()); } /// @@ -29,8 +29,8 @@ public FunctionIdentifier(uint scopeId, Utf8String name, CodeViewTypeRecord func : base(0) { ScopeId = scopeId; - _name = new LazyVariable(name); - _functionType = new LazyVariable(functionType); + _name = new LazyVariable(name); + _functionType = new LazyVariable(functionType); } /// @@ -50,8 +50,8 @@ public uint ScopeId /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// @@ -59,8 +59,8 @@ public Utf8String? Name /// public CodeViewTypeRecord? FunctionType { - get => _functionType.Value; - set => _functionType.Value = value; + get => _functionType.GetValue(this); + set => _functionType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs index 08fc5adcd..7618d3aaa 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs @@ -7,10 +7,10 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class MemberFunctionLeaf : CodeViewLeaf, ITpiLeaf { - private readonly LazyVariable _returnType; - private readonly LazyVariable _declaringType; - private readonly LazyVariable _thisType; - private readonly LazyVariable _argumentList; + private readonly LazyVariable _returnType; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _thisType; + private readonly LazyVariable _argumentList; /// /// Initializes an empty member function. @@ -19,10 +19,10 @@ public class MemberFunctionLeaf : CodeViewLeaf, ITpiLeaf protected MemberFunctionLeaf(uint typeIndex) : base(typeIndex) { - _returnType = new LazyVariable(GetReturnType); - _declaringType = new LazyVariable(GetDeclaringType); - _thisType = new LazyVariable(GetThisType); - _argumentList = new LazyVariable(GetArguments); + _returnType = new LazyVariable(x => x.GetReturnType()); + _declaringType = new LazyVariable(x => x.GetDeclaringType()); + _thisType = new LazyVariable(x => x.GetThisType()); + _argumentList = new LazyVariable(x => x.GetArguments()); } /// @@ -34,10 +34,10 @@ protected MemberFunctionLeaf(uint typeIndex) 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); + _returnType = new LazyVariable(returnType); + _declaringType = new LazyVariable(declaringType); + _thisType = new LazyVariable(default(CodeViewTypeRecord)); + _argumentList = new LazyVariable(arguments); CallingConvention = CodeViewCallingConvention.NearC; Attributes = 0; ThisAdjuster = 0; @@ -51,8 +51,8 @@ public MemberFunctionLeaf(CodeViewTypeRecord returnType, CodeViewTypeRecord decl /// public CodeViewTypeRecord? ReturnType { - get => _returnType.Value; - set => _returnType.Value = value; + get => _returnType.GetValue(this); + set => _returnType.SetValue(value); } /// @@ -60,8 +60,8 @@ public CodeViewTypeRecord? ReturnType /// public CodeViewTypeRecord? DeclaringType { - get => _declaringType.Value; - set => _declaringType.Value = value; + get => _declaringType.GetValue(this); + set => _declaringType.SetValue(value); } /// @@ -69,8 +69,8 @@ public CodeViewTypeRecord? DeclaringType /// public CodeViewTypeRecord? ThisType { - get => _thisType.Value; - set => _thisType.Value = value; + get => _thisType.GetValue(this); + set => _thisType.SetValue(value); } /// @@ -96,8 +96,8 @@ public MemberFunctionAttributes Attributes /// public ArgumentListLeaf? Arguments { - get => _argumentList.Value; - set => _argumentList.Value = value; + get => _argumentList.GetValue(this); + set => _argumentList.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs index 447296798..83b4aa210 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs @@ -5,14 +5,14 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class MethodListEntry { - private readonly LazyVariable _function; + private readonly LazyVariable _function; /// /// Initializes an empty method list entry. /// protected MethodListEntry() { - _function = new LazyVariable(GetFunction); + _function = new LazyVariable(x => x.GetFunction()); } /// @@ -23,7 +23,7 @@ protected MethodListEntry() public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function) { Attributes = attributes; - _function = new LazyVariable(function); + _function = new LazyVariable(function); VTableOffset = 0; } @@ -36,7 +36,7 @@ public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf fu public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function, uint vTableOffset) { Attributes = attributes; - _function = new LazyVariable(function); + _function = new LazyVariable(function); VTableOffset = vTableOffset; } @@ -54,8 +54,8 @@ public CodeViewFieldAttributes Attributes /// public MemberFunctionLeaf? Function { - get => _function.Value; - set => _function.Value = value; + get => _function.GetValue(this); + set => _function.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs index c70e50ed1..cb121c474 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class ModifierTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _baseType; + private readonly LazyVariable _baseType; /// /// Initializes a new empty modifier type. @@ -14,7 +14,7 @@ public class ModifierTypeRecord : CodeViewTypeRecord protected ModifierTypeRecord(uint typeIndex) : base(typeIndex) { - _baseType = new LazyVariable(GetBaseType); + _baseType = new LazyVariable(x => x.GetBaseType()); } /// @@ -25,7 +25,7 @@ protected ModifierTypeRecord(uint typeIndex) public ModifierTypeRecord(CodeViewTypeRecord type, ModifierAttributes attributes) : base(0) { - _baseType = new LazyVariable(type); + _baseType = new LazyVariable(type); Attributes = attributes; } @@ -37,8 +37,8 @@ public ModifierTypeRecord(CodeViewTypeRecord type, ModifierAttributes attributes /// public CodeViewTypeRecord BaseType { - get => _baseType.Value; - set => _baseType.Value = value; + get => _baseType.GetValue(this); + set => _baseType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs index 91bca6ee3..e240cada7 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class NestedTypeField : CodeViewNamedField { - private readonly LazyVariable _type; + private readonly LazyVariable _type; /// /// Initializes an empty nested type. @@ -14,7 +14,7 @@ public class NestedTypeField : CodeViewNamedField protected NestedTypeField(uint typeIndex) : base(typeIndex) { - _type = new LazyVariable(GetNestedType); + _type = new LazyVariable(x => x.GetNestedType()); } /// @@ -25,7 +25,7 @@ protected NestedTypeField(uint typeIndex) public NestedTypeField(CodeViewTypeRecord type, Utf8String name) : base(0) { - _type = new LazyVariable(type); + _type = new LazyVariable(type); Name = name; Attributes = 0; } @@ -39,7 +39,7 @@ public NestedTypeField(CodeViewTypeRecord type, Utf8String name) public NestedTypeField(CodeViewTypeRecord type, Utf8String name, CodeViewFieldAttributes attributes) : base(0) { - _type = new LazyVariable(type); + _type = new LazyVariable(type); Name = name; Attributes = attributes; } @@ -54,8 +54,8 @@ public NestedTypeField(CodeViewTypeRecord type, Utf8String name, CodeViewFieldAt /// public CodeViewTypeRecord? Type { - get => _type.Value; - set => _type.Value = value; + get => _type.GetValue(this); + set => _type.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs index 08bdb113f..65afaa7c4 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class NonOverloadedMethod : CodeViewNamedField { - private readonly LazyVariable _function; + private readonly LazyVariable _function; /// /// Initializes an empty non-overloaded method. @@ -14,7 +14,7 @@ public class NonOverloadedMethod : CodeViewNamedField protected NonOverloadedMethod(uint typeIndex) : base(typeIndex) { - _function = new LazyVariable(GetFunction); + _function = new LazyVariable(x => x.GetFunction()); } /// @@ -26,7 +26,7 @@ protected NonOverloadedMethod(uint typeIndex) public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, MemberFunctionLeaf function) : base(0) { - _function = new LazyVariable(function); + _function = new LazyVariable(function); Attributes = attributes; Name = name; } @@ -41,7 +41,7 @@ public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, uint vTableOffset, MemberFunctionLeaf function) : base(0) { - _function = new LazyVariable(function); + _function = new LazyVariable(function); Attributes = attributes; Name = name; VTableOffset = vTableOffset; @@ -55,8 +55,8 @@ public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, /// public MemberFunctionLeaf? Function { - get => _function.Value; - set => _function.Value = value; + get => _function.GetValue(this); + set => _function.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs index a6c17cea0..ed5e4e50e 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class OverloadedMethod : CodeViewNamedField { - private readonly LazyVariable _methods; + private readonly LazyVariable _methods; /// /// Initializes an empty overloaded method. @@ -14,7 +14,7 @@ public class OverloadedMethod : CodeViewNamedField protected OverloadedMethod(uint typeIndex) : base(typeIndex) { - _methods = new LazyVariable(GetMethods); + _methods = new LazyVariable(x => x.GetMethods()); } /// @@ -23,7 +23,7 @@ protected OverloadedMethod(uint typeIndex) public OverloadedMethod() : base(0) { - _methods = new LazyVariable(new MethodListLeaf()); + _methods = new LazyVariable(new MethodListLeaf()); } /// @@ -32,7 +32,7 @@ public OverloadedMethod() public OverloadedMethod(MethodListLeaf methods) : base(0) { - _methods = new LazyVariable(methods); + _methods = new LazyVariable(methods); } /// @@ -41,7 +41,7 @@ public OverloadedMethod(MethodListLeaf methods) public OverloadedMethod(params MethodListEntry[] methods) : base(0) { - _methods = new LazyVariable(new MethodListLeaf(methods)); + _methods = new LazyVariable(new MethodListLeaf(methods)); } /// @@ -52,8 +52,8 @@ public OverloadedMethod(params MethodListEntry[] methods) /// public MethodListLeaf? Methods { - get => _methods.Value; - set => _methods.Value = value; + get => _methods.GetValue(this); + set => _methods.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs index 740e792cd..dc77efcb9 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class PointerTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _baseType; + private readonly LazyVariable _baseType; /// /// Initializes a new empty pointer type. @@ -14,7 +14,7 @@ public class PointerTypeRecord : CodeViewTypeRecord protected PointerTypeRecord(uint typeIndex) : base(typeIndex) { - _baseType = new LazyVariable(GetBaseType); + _baseType = new LazyVariable(x => x.GetBaseType()); } /// @@ -25,7 +25,7 @@ protected PointerTypeRecord(uint typeIndex) public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes) : base(0) { - _baseType = new LazyVariable(type); + _baseType = new LazyVariable(type); Attributes = attributes; } @@ -38,7 +38,7 @@ public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes) public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes, byte size) : base(0) { - _baseType = new LazyVariable(type); + _baseType = new LazyVariable(type); Attributes = attributes; Size = size; } @@ -51,8 +51,8 @@ public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes, /// public CodeViewTypeRecord BaseType { - get => _baseType.Value; - set => _baseType.Value = value; + get => _baseType.GetValue(this); + set => _baseType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs index b973fcccb..82e81dd84 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs @@ -7,8 +7,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class ProcedureTypeRecord : CodeViewTypeRecord { - private readonly LazyVariable _returnType; - private readonly LazyVariable _argumentList; + private readonly LazyVariable _returnType; + private readonly LazyVariable _argumentList; /// /// Initializes an empty procedure type. @@ -17,8 +17,8 @@ public class ProcedureTypeRecord : CodeViewTypeRecord protected ProcedureTypeRecord(uint typeIndex) : base(typeIndex) { - _returnType = new LazyVariable(GetReturnType); - _argumentList = new LazyVariable(GetArguments); + _returnType = new LazyVariable(x => x.GetReturnType()); + _argumentList = new LazyVariable(x => x.GetArguments()); } /// @@ -31,8 +31,8 @@ public ProcedureTypeRecord(CodeViewCallingConvention callingConvention, CodeView : base(0) { CallingConvention = callingConvention; - _returnType = new LazyVariable(returnType); - _argumentList = new LazyVariable(arguments); + _returnType = new LazyVariable(returnType); + _argumentList = new LazyVariable(arguments); } /// @@ -43,8 +43,8 @@ public ProcedureTypeRecord(CodeViewCallingConvention callingConvention, CodeView /// public CodeViewTypeRecord? ReturnType { - get => _returnType.Value; - set => _returnType.Value = value; + get => _returnType.GetValue(this); + set => _returnType.SetValue(value); } /// @@ -70,8 +70,8 @@ public MemberFunctionAttributes Attributes /// public ArgumentListLeaf? Arguments { - get => _argumentList.Value; - set => _argumentList.Value = value; + get => _argumentList.GetValue(this); + set => _argumentList.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StringIdentifier.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StringIdentifier.cs index 2de1b1bf2..112a9932b 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/StringIdentifier.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StringIdentifier.cs @@ -5,8 +5,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class StringIdentifier : CodeViewLeaf, IIpiLeaf { - private readonly LazyVariable _value; - private readonly LazyVariable _subStrings; + private readonly LazyVariable _value; + private readonly LazyVariable _subStrings; /// /// Initializes an empty String ID entry. @@ -15,8 +15,8 @@ public class StringIdentifier : CodeViewLeaf, IIpiLeaf protected StringIdentifier(uint typeIndex) : base(typeIndex) { - _value = new LazyVariable(GetValue); - _subStrings = new LazyVariable(GetSubStrings); + _value = new LazyVariable(x =>x .GetValue()); + _subStrings = new LazyVariable(x =>x .GetSubStrings()); } /// @@ -36,8 +36,8 @@ public StringIdentifier(Utf8String value) public StringIdentifier(Utf8String value, SubStringListLeaf? subStrings) : base(0) { - _value = new LazyVariable(value); - _subStrings = new LazyVariable(subStrings); + _value = new LazyVariable(value); + _subStrings = new LazyVariable(subStrings); } /// @@ -48,8 +48,8 @@ public StringIdentifier(Utf8String value, SubStringListLeaf? subStrings) /// public Utf8String Value { - get => _value.Value; - set => _value.Value = value; + get => _value.GetValue(this); + set => _value.SetValue(value); } /// @@ -57,8 +57,8 @@ public Utf8String Value /// public SubStringListLeaf? SubStrings { - get => _subStrings.Value; - set => _subStrings.Value = value; + get => _subStrings.GetValue(this); + set => _subStrings.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs index a28d73ceb..b51fa686b 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class UnionTypeRecord : CodeViewCompositeTypeRecord { - private readonly LazyVariable _uniqueName; + private readonly LazyVariable _uniqueName; /// /// Initializes an empty union type. @@ -14,7 +14,7 @@ public class UnionTypeRecord : CodeViewCompositeTypeRecord protected UnionTypeRecord(uint typeIndex) : base(typeIndex) { - _uniqueName = new LazyVariable(GetUniqueName); + _uniqueName = new LazyVariable(x => x.GetUniqueName()); } /// @@ -24,7 +24,7 @@ protected UnionTypeRecord(uint typeIndex) public UnionTypeRecord(ulong size) : base(0) { - _uniqueName = new LazyVariable(Utf8String.Empty); + _uniqueName = new LazyVariable(Utf8String.Empty); Size = size; } @@ -45,8 +45,8 @@ public ulong Size /// public Utf8String UniqueName { - get => _uniqueName.Value; - set => _uniqueName.Value = value; + get => _uniqueName.GetValue(this); + set => _uniqueName.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs index 99d5edf0e..58be950e6 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs @@ -5,8 +5,8 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class VBaseClassField : CodeViewField { - private readonly LazyVariable _baseType; - private readonly LazyVariable _basePointerType; + private readonly LazyVariable _baseType; + private readonly LazyVariable _basePointerType; /// /// Initializes a new empty virtual base class field. @@ -15,8 +15,8 @@ public class VBaseClassField : CodeViewField protected VBaseClassField(uint typeIndex) : base(typeIndex) { - _baseType = new LazyVariable(GetBaseType); - _basePointerType = new LazyVariable(GetBasePointerType); + _baseType = new LazyVariable(x => x.GetBaseType()); + _basePointerType = new LazyVariable(x => x.GetBasePointerType()); } /// @@ -35,8 +35,8 @@ public VBaseClassField( bool isIndirect) : base(0) { - _baseType = new LazyVariable(baseType); - _basePointerType = new LazyVariable(pointerType); + _baseType = new LazyVariable(baseType); + _basePointerType = new LazyVariable(pointerType); PointerOffset = pointerOffset; TableOffset = tableOffset; IsIndirect = isIndirect; @@ -61,8 +61,8 @@ public bool IsIndirect /// public CodeViewTypeRecord? Type { - get => _baseType.Value; - set => _baseType.Value = value; + get => _baseType.GetValue(this); + set => _baseType.SetValue(value); } /// @@ -70,8 +70,8 @@ public CodeViewTypeRecord? Type /// public CodeViewTypeRecord? PointerType { - get => _basePointerType.Value; - set => _basePointerType.Value = value; + get => _basePointerType.GetValue(this); + set => _basePointerType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs index 11355cad4..5d30879a6 100644 --- a/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Leaves; /// public class VTableField : CodeViewField { - private readonly LazyVariable _type; + private readonly LazyVariable _type; /// /// Initializes an empty virtual function table field. @@ -14,7 +14,7 @@ public class VTableField : CodeViewField protected VTableField(uint typeIndex) : base(typeIndex) { - _type = new LazyVariable(GetPointerType); + _type = new LazyVariable(x => x.GetPointerType()); } /// @@ -24,7 +24,7 @@ protected VTableField(uint typeIndex) public VTableField(CodeViewTypeRecord pointerType) : base(0) { - _type = new LazyVariable(pointerType); + _type = new LazyVariable(pointerType); } /// @@ -35,8 +35,8 @@ public VTableField(CodeViewTypeRecord pointerType) /// public CodeViewTypeRecord? PointerType { - get => _type.Value; - set => _type.Value = value; + get => _type.GetValue(this); + set => _type.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index 247bd21ac..581e3932f 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -20,8 +20,8 @@ public class DbiStream : SegmentBase private IList? _modules; private IList? _sectionContributions; private IList? _sectionMaps; - private readonly LazyVariable _typeServerMapStream; - private readonly LazyVariable _ecStream; + private readonly LazyVariable _typeServerMapStream; + private readonly LazyVariable _ecStream; private IList? _sourceFiles; private IList? _extraStreamIndices; @@ -30,8 +30,8 @@ public class DbiStream : SegmentBase /// public DbiStream() { - _typeServerMapStream = new LazyVariable(GetTypeServerMapStream); - _ecStream = new LazyVariable(GetECStream); + _typeServerMapStream = new LazyVariable(x => x.GetTypeServerMapStream()); + _ecStream = new LazyVariable(x => x.GetECStream()); IsNewVersionFormat = true; } @@ -228,8 +228,8 @@ public IList SectionMaps /// public ISegment? TypeServerMapStream { - get => _typeServerMapStream.Value; - set => _typeServerMapStream.Value = value; + get => _typeServerMapStream.GetValue(this); + set => _typeServerMapStream.SetValue(value); } /// @@ -241,8 +241,8 @@ public ISegment? TypeServerMapStream /// public ISegment? ECStream { - get => _ecStream.Value; - set => _ecStream.Value = value; + get => _ecStream.GetValue(this); + set => _ecStream.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Modi/ModiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Modi/ModiStream.cs index 5f788fa54..c4d6bde40 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Modi/ModiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Modi/ModiStream.cs @@ -8,20 +8,20 @@ namespace AsmResolver.Symbols.Pdb.Metadata.Modi; /// public class ModiStream : SegmentBase { - private readonly LazyVariable _symbols; - private readonly LazyVariable _c11LineInfo; - private readonly LazyVariable _c13LineInfo; - private readonly LazyVariable _globalReferences; + private readonly LazyVariable _symbols; + private readonly LazyVariable _c11LineInfo; + private readonly LazyVariable _c13LineInfo; + private readonly LazyVariable _globalReferences; /// /// Creates a new empty module info stream. /// public ModiStream() { - _symbols = new LazyVariable(GetSymbols); - _c11LineInfo = new LazyVariable(GetC11LineInfo); - _c13LineInfo = new LazyVariable(GetC13LineInfo); - _globalReferences = new LazyVariable(GetGlobalReferences); + _symbols = new LazyVariable(x => x.GetSymbols()); + _c11LineInfo = new LazyVariable(x => x.GetC11LineInfo()); + _c13LineInfo = new LazyVariable(x => x.GetC13LineInfo()); + _globalReferences = new LazyVariable(x => x.GetGlobalReferences()); } /// @@ -41,8 +41,8 @@ public uint Signature /// public IReadableSegment? Symbols { - get => _symbols.Value; - set => _symbols.Value = value; + get => _symbols.GetValue(this); + set => _symbols.SetValue(value); } /// @@ -50,8 +50,8 @@ public IReadableSegment? Symbols /// public IReadableSegment? C11LineInfo { - get => _c11LineInfo.Value; - set => _c11LineInfo.Value = value; + get => _c11LineInfo.GetValue(this); + set => _c11LineInfo.SetValue(value); } /// @@ -59,8 +59,8 @@ public IReadableSegment? C11LineInfo /// public IReadableSegment? C13LineInfo { - get => _c13LineInfo.Value; - set => _c13LineInfo.Value = value; + get => _c13LineInfo.GetValue(this); + set => _c13LineInfo.SetValue(value); } /// @@ -71,8 +71,8 @@ public IReadableSegment? C13LineInfo /// public IReadableSegment? GlobalReferences { - get => _globalReferences.Value; - set => _globalReferences.Value = value; + get => _globalReferences.GetValue(this); + set => _globalReferences.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/PdbModule.cs b/src/AsmResolver.Symbols.Pdb/PdbModule.cs index bd76dae1c..0ed64d247 100644 --- a/src/AsmResolver.Symbols.Pdb/PdbModule.cs +++ b/src/AsmResolver.Symbols.Pdb/PdbModule.cs @@ -10,10 +10,10 @@ namespace AsmResolver.Symbols.Pdb; /// public class PdbModule : ICodeViewSymbolProvider { - private readonly LazyVariable _name; - private readonly LazyVariable _objectFileName; + private readonly LazyVariable _name; + private readonly LazyVariable _objectFileName; private IList? _sourceFiles; - private readonly LazyVariable _sectionContribution; + private readonly LazyVariable _sectionContribution; private IList? _symbols; @@ -22,9 +22,9 @@ public class PdbModule : ICodeViewSymbolProvider /// protected PdbModule() { - _name = new LazyVariable(GetName); - _objectFileName = new LazyVariable(GetObjectFileName); - _sectionContribution = new LazyVariable(GetSectionContribution); + _name = new LazyVariable(x => x.GetName()); + _objectFileName = new LazyVariable(x => x.GetObjectFileName()); + _sectionContribution = new LazyVariable(x => x.GetSectionContribution()); } /// @@ -43,9 +43,9 @@ public PdbModule(Utf8String name) /// The path to the object file. public PdbModule(Utf8String name, Utf8String objectFileName) { - _name = new LazyVariable(name); - _objectFileName = new LazyVariable(objectFileName); - _sectionContribution = new LazyVariable(new SectionContribution()); + _name = new LazyVariable(name); + _objectFileName = new LazyVariable(objectFileName); + _sectionContribution = new LazyVariable(new SectionContribution()); } /// @@ -57,8 +57,8 @@ public PdbModule(Utf8String name, Utf8String objectFileName) /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// @@ -70,8 +70,8 @@ public Utf8String? Name /// public Utf8String? ObjectFileName { - get => _objectFileName.Value; - set => _objectFileName.Value = value; + get => _objectFileName.GetValue(this); + set => _objectFileName.SetValue(value); } /// @@ -93,8 +93,8 @@ public IList SourceFiles /// public SectionContribution SectionContribution { - get => _sectionContribution.Value; - set => _sectionContribution.Value = value; + get => _sectionContribution.GetValue(this); + set => _sectionContribution.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/BasePointerRelativeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/BasePointerRelativeSymbol.cs index 83bb21064..c17fed782 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/BasePointerRelativeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/BasePointerRelativeSymbol.cs @@ -7,16 +7,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class BasePointerRelativeSymbol : CodeViewSymbol, IRegisterRelativeSymbol { - private readonly LazyVariable _variableType; - private readonly LazyVariable _name; + private readonly LazyVariable _variableType; + private readonly LazyVariable _name; /// /// Initializes an empty base-pointer relative symbol. /// protected BasePointerRelativeSymbol() { - _name = new LazyVariable(GetName); - _variableType = new LazyVariable(GetVariableType); + _name = new LazyVariable(x => x.GetName()); + _variableType = new LazyVariable(x => x.GetVariableType()); } /// @@ -27,8 +27,8 @@ protected BasePointerRelativeSymbol() /// The type of variable the symbol stores. public BasePointerRelativeSymbol(Utf8String name, CodeViewTypeRecord variableType, int offset) { - _name = new LazyVariable(name); - _variableType = new LazyVariable(variableType); + _name = new LazyVariable(name); + _variableType = new LazyVariable(variableType); Offset = offset; } @@ -49,8 +49,8 @@ public int Offset /// public CodeViewTypeRecord? VariableType { - get => _variableType.Value; - set => _variableType.Value = value; + get => _variableType.GetValue(this); + set => _variableType.SetValue(value); } /// @@ -58,8 +58,8 @@ public CodeViewTypeRecord? VariableType /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/BuildInfoSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/BuildInfoSymbol.cs index 60ed99b80..a9852eb25 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/BuildInfoSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/BuildInfoSymbol.cs @@ -7,14 +7,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class BuildInfoSymbol : CodeViewSymbol { - private readonly LazyVariable _info; + private readonly LazyVariable _info; /// /// Initializes an empty build information symbol. /// protected BuildInfoSymbol() { - _info = new LazyVariable(GetInfo); + _info = new LazyVariable(x => x.GetInfo()); } /// @@ -23,7 +23,7 @@ protected BuildInfoSymbol() /// The information to wrap. public BuildInfoSymbol(BuildInfoLeaf info) { - _info = new LazyVariable(info); + _info = new LazyVariable(info); } /// @@ -34,8 +34,8 @@ public BuildInfoSymbol(BuildInfoLeaf info) /// public BuildInfoLeaf? Info { - get => _info.Value; - set => _info.Value = value; + get => _info.GetValue(this); + set => _info.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/CallSiteSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CallSiteSymbol.cs index a2eb2e41e..ff88e22c6 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/CallSiteSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CallSiteSymbol.cs @@ -7,14 +7,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class CallSiteSymbol : CodeViewSymbol { - private readonly LazyVariable _functionType; + private readonly LazyVariable _functionType; /// /// Initializes an empty call site symbol. /// protected CallSiteSymbol() { - _functionType = new LazyVariable(GetFunctionType); + _functionType = new LazyVariable(x => x.GetFunctionType()); } /// @@ -27,7 +27,7 @@ public CallSiteSymbol(ushort sectionIndex, int offset, CodeViewTypeRecord functi { SectionIndex = sectionIndex; Offset = offset; - _functionType = new LazyVariable(functionType); + _functionType = new LazyVariable(functionType); } /// @@ -56,8 +56,8 @@ public int Offset /// public CodeViewTypeRecord? FunctionType { - get => _functionType.Value; - set => _functionType.Value = value; + get => _functionType.GetValue(this); + set => _functionType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/CoffGroupSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CoffGroupSymbol.cs index 6f04d1c82..be69bda15 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/CoffGroupSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CoffGroupSymbol.cs @@ -7,14 +7,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class CoffGroupSymbol : CodeViewSymbol { - private readonly LazyVariable _name; + private readonly LazyVariable _name; /// /// Initializes an empty COFF group symbol. /// protected CoffGroupSymbol() { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -27,7 +27,7 @@ protected CoffGroupSymbol() /// The characteristics describing the group. public CoffGroupSymbol(Utf8String name, ushort segmentIndex, uint offset, uint size, SectionFlags characteristics) { - _name = new LazyVariable(name); + _name = new LazyVariable(name); SegmentIndex = segmentIndex; Offset = offset; Size = size; @@ -78,8 +78,8 @@ public uint Offset /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/CompileSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CompileSymbol.cs index cb95e2b29..38993eaef 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/CompileSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/CompileSymbol.cs @@ -5,14 +5,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public abstract class CompileSymbol : CodeViewSymbol { - private readonly LazyVariable _compilerVersion; + private readonly LazyVariable _compilerVersion; /// /// Initializes an empty compile symbol. /// protected CompileSymbol() { - _compilerVersion = new LazyVariable(GetCompilerVersion); + _compilerVersion = new LazyVariable(x => x.GetCompilerVersion()); } /// @@ -101,8 +101,8 @@ public ushort BackEndBuildVersion /// public Utf8String CompilerVersion { - get => _compilerVersion.Value; - set => _compilerVersion.Value = value; + get => _compilerVersion.GetValue(this); + set => _compilerVersion.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs index dffc6183a..261f64295 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs @@ -7,16 +7,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class ConstantSymbol : CodeViewSymbol { - private readonly LazyVariable _name; - private readonly LazyVariable _type; + private readonly LazyVariable _name; + private readonly LazyVariable _type; /// /// Initializes a named constant /// protected ConstantSymbol() { - _name = new LazyVariable(GetName); - _type = new LazyVariable(GetConstantType); + _name = new LazyVariable(x => x.GetName()); + _type = new LazyVariable(x => x.GetConstantType()); } /// @@ -27,8 +27,8 @@ protected ConstantSymbol() /// The value to assign to the constant. public ConstantSymbol(Utf8String name, CodeViewTypeRecord type, ushort value) { - _name = new LazyVariable(name); - _type = new LazyVariable(type); + _name = new LazyVariable(name); + _type = new LazyVariable(type); Value = value; } @@ -40,8 +40,8 @@ public ConstantSymbol(Utf8String name, CodeViewTypeRecord type, ushort value) /// public CodeViewTypeRecord Type { - get => _type.Value; - set => _type.Value = value; + get => _type.GetValue(this); + set => _type.SetValue(value); } /// @@ -58,8 +58,8 @@ public ushort Value /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/DataSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/DataSymbol.cs index e2c29c5cb..4f34d49f6 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/DataSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/DataSymbol.cs @@ -7,16 +7,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class DataSymbol : CodeViewSymbol, IVariableSymbol { - private readonly LazyVariable _name; - private readonly LazyVariable _variableType; + private readonly LazyVariable _name; + private readonly LazyVariable _variableType; /// /// Initializes an empty data symbol. /// protected DataSymbol() { - _name = new LazyVariable(GetName); - _variableType = new LazyVariable(GetVariableType); + _name = new LazyVariable(x => x.GetName()); + _variableType = new LazyVariable(x => x.GetVariableType()); } /// @@ -26,8 +26,8 @@ protected DataSymbol() /// The data type of the symbol. public DataSymbol(Utf8String name, CodeViewTypeRecord variableType) { - _name = new LazyVariable(name); - _variableType = new LazyVariable(variableType); + _name = new LazyVariable(name); + _variableType = new LazyVariable(variableType); } /// @@ -74,15 +74,15 @@ public uint Offset /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// public CodeViewTypeRecord? VariableType { - get => _variableType.Value; - set => _variableType.Value = value; + get => _variableType.GetValue(this); + set => _variableType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/FileStaticSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/FileStaticSymbol.cs index a0a408b52..5a2f3525c 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/FileStaticSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/FileStaticSymbol.cs @@ -7,16 +7,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class FileStaticSymbol : CodeViewSymbol, IVariableSymbol { - private readonly LazyVariable _name; - private readonly LazyVariable _variableType; + private readonly LazyVariable _name; + private readonly LazyVariable _variableType; /// /// Initializes an empty file static symbol. /// protected FileStaticSymbol() { - _name = new LazyVariable(GetName); - _variableType = new LazyVariable(GetVariableType); + _name = new LazyVariable(x => x.GetName()); + _variableType = new LazyVariable(x => x.GetVariableType()); } /// @@ -28,8 +28,8 @@ protected FileStaticSymbol() public FileStaticSymbol(Utf8String name, CodeViewTypeRecord variableType, LocalAttributes attributes) { Attributes = attributes; - _name = new LazyVariable(name); - _variableType = new LazyVariable(variableType); + _name = new LazyVariable(name); + _variableType = new LazyVariable(variableType); } /// @@ -56,15 +56,15 @@ public LocalAttributes Attributes /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// public CodeViewTypeRecord? VariableType { - get => _variableType.Value; - set => _variableType.Value = value; + get => _variableType.GetValue(this); + set => _variableType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/InlineSiteSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/InlineSiteSymbol.cs index d4fb13867..dd052121f 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/InlineSiteSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/InlineSiteSymbol.cs @@ -9,7 +9,7 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class InlineSiteSymbol : CodeViewSymbol, IScopeCodeViewSymbol { - private readonly LazyVariable _inlinee; + private readonly LazyVariable _inlinee; private IList? _symbols; private IList? _annotations; @@ -19,7 +19,7 @@ public class InlineSiteSymbol : CodeViewSymbol, IScopeCodeViewSymbol /// protected InlineSiteSymbol() { - _inlinee = new LazyVariable(GetInlinee); + _inlinee = new LazyVariable(x => x.GetInlinee()); } /// @@ -28,7 +28,7 @@ protected InlineSiteSymbol() /// The function that is being inlined. public InlineSiteSymbol(FunctionIdentifier inlinee) { - _inlinee = new LazyVariable(inlinee); + _inlinee = new LazyVariable(inlinee); } /// @@ -50,8 +50,8 @@ public IList Symbols /// public FunctionIdentifier? Inlinee { - get => _inlinee.Value; - set => _inlinee.Value = value; + get => _inlinee.GetValue(this); + set => _inlinee.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/LabelSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/LabelSymbol.cs index ca42dca92..11f509d88 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/LabelSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/LabelSymbol.cs @@ -5,14 +5,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class LabelSymbol : CodeViewSymbol { - private readonly LazyVariable _name; + private readonly LazyVariable _name; /// /// Initializes an empty label symbol. /// protected LabelSymbol() { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -24,7 +24,7 @@ protected LabelSymbol() /// The attributes describing the label. public LabelSymbol(Utf8String name, ushort segmentIndex, uint offset, ProcedureAttributes attributes) { - _name = new LazyVariable(name); + _name = new LazyVariable(name); SegmentIndex = segmentIndex; Offset = offset; Attributes = attributes; @@ -65,8 +65,8 @@ public ProcedureAttributes Attributes /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/LocalSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/LocalSymbol.cs index 2c9cdae46..d53c0f786 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/LocalSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/LocalSymbol.cs @@ -7,16 +7,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class LocalSymbol : CodeViewSymbol, IVariableSymbol { - private readonly LazyVariable _variableType; - private readonly LazyVariable _name; + private readonly LazyVariable _variableType; + private readonly LazyVariable _name; /// /// Initializes an empty local variable symbol. /// protected LocalSymbol() { - _variableType = new LazyVariable(GetVariableType); - _name = new LazyVariable(GetName); + _variableType = new LazyVariable(x => x.GetVariableType()); + _name = new LazyVariable(x => x.GetName()); } /// @@ -27,8 +27,8 @@ protected LocalSymbol() /// The attributes describing the variable. public LocalSymbol(Utf8String? name, CodeViewTypeRecord? variableType, LocalAttributes attributes) { - _variableType = new LazyVariable(variableType); - _name = new LazyVariable(name); + _variableType = new LazyVariable(variableType); + _name = new LazyVariable(name); Attributes = attributes; } @@ -38,8 +38,8 @@ public LocalSymbol(Utf8String? name, CodeViewTypeRecord? variableType, LocalAttr /// public CodeViewTypeRecord? VariableType { - get => _variableType.Value; - set => _variableType.Value = value; + get => _variableType.GetValue(this); + set => _variableType.SetValue(value); } /// @@ -54,8 +54,8 @@ public LocalAttributes Attributes /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/ObjectNameSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ObjectNameSymbol.cs index d40a629dd..cfe0df8be 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ObjectNameSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ObjectNameSymbol.cs @@ -5,14 +5,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class ObjectNameSymbol : CodeViewSymbol { - private readonly LazyVariable _name; + private readonly LazyVariable _name; /// /// Initializes an empty object name symbol. /// protected ObjectNameSymbol() { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -23,7 +23,7 @@ protected ObjectNameSymbol() public ObjectNameSymbol(uint signature, Utf8String name) { Signature = signature; - _name = new LazyVariable(name); + _name = new LazyVariable(name); } /// @@ -43,8 +43,8 @@ public uint Signature /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/ProcedureReferenceSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ProcedureReferenceSymbol.cs index 0ee3f5782..d1964af4e 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ProcedureReferenceSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ProcedureReferenceSymbol.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class ProcedureReferenceSymbol : CodeViewSymbol { - private readonly LazyVariable _name; + private readonly LazyVariable _name; private readonly bool _local; /// @@ -14,7 +14,7 @@ public class ProcedureReferenceSymbol : CodeViewSymbol /// If true, this represents a local procedure reference. protected ProcedureReferenceSymbol(bool local) { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); _local = local; } @@ -31,18 +31,14 @@ public ProcedureReferenceSymbol(uint checksum, uint offset, ushort module, Utf8S Checksum = checksum; Offset = offset; Module = module; - _name = new LazyVariable(name); + _name = new LazyVariable(name); _local = local; } /// - public override CodeViewSymbolType CodeViewSymbolType - { - get - { - return _local ? CodeViewSymbolType.LProcRef : CodeViewSymbolType.ProcRef; - } - } + public override CodeViewSymbolType CodeViewSymbolType => _local + ? CodeViewSymbolType.LProcRef + : CodeViewSymbolType.ProcRef; /// /// Is the symbol a Local Procedure Reference? @@ -83,8 +79,8 @@ public ushort Module /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/ProcedureSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ProcedureSymbol.cs index 8786de18b..699a39729 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ProcedureSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ProcedureSymbol.cs @@ -9,8 +9,8 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class ProcedureSymbol : CodeViewSymbol, IScopeCodeViewSymbol { - private readonly LazyVariable _name; - private readonly LazyVariable _type; + private readonly LazyVariable _name; + private readonly LazyVariable _type; private IList? _symbols; @@ -19,8 +19,8 @@ public class ProcedureSymbol : CodeViewSymbol, IScopeCodeViewSymbol /// protected ProcedureSymbol() { - _name = new LazyVariable(GetName); - _type = new LazyVariable(GetFunctionType); + _name = new LazyVariable(x => x.GetName()); + _type = new LazyVariable(x => x.GetFunctionType()); } /// @@ -30,8 +30,8 @@ protected ProcedureSymbol() /// The function identifier of the procedure. public ProcedureSymbol(Utf8String name, FunctionIdentifier id) { - _name = new LazyVariable(name); - _type = new LazyVariable(id); + _name = new LazyVariable(name); + _type = new LazyVariable(id); } /// @@ -41,8 +41,8 @@ public ProcedureSymbol(Utf8String name, FunctionIdentifier id) /// The type describing the shape of the procedure. public ProcedureSymbol(Utf8String name, ProcedureTypeRecord type) { - _name = new LazyVariable(name); - _type = new LazyVariable(type); + _name = new LazyVariable(name); + _type = new LazyVariable(type); } /// @@ -124,8 +124,8 @@ public uint DebugEndOffset /// public CodeViewLeaf? Type { - get => _type.Value; - set => _type.Value = value; + get => _type.GetValue(this); + set => _type.SetValue(value); } /// @@ -178,8 +178,8 @@ public ProcedureAttributes Attributes /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs index 8d89e4212..c70aa1ec5 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs @@ -5,14 +5,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class PublicSymbol : CodeViewSymbol { - private readonly LazyVariable _name; + private readonly LazyVariable _name; /// /// Initializes a new empty public symbol. /// protected PublicSymbol() { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -26,7 +26,7 @@ public PublicSymbol(ushort segmentIndex, uint offset, Utf8String name, PublicSym { SegmentIndex = segmentIndex; Offset = offset; - _name = new LazyVariable(name); + _name = new LazyVariable(name); Attributes = attributes; } @@ -105,8 +105,8 @@ public bool IsMsil /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeSymbol.cs index 4871ea858..1a868d5bd 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/RegisterRelativeSymbol.cs @@ -7,16 +7,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class RegisterRelativeSymbol : CodeViewSymbol, IRegisterRelativeSymbol { - private readonly LazyVariable _variableType; - private readonly LazyVariable _name; + private readonly LazyVariable _variableType; + private readonly LazyVariable _name; /// /// Initializes an empty relative register symbol. /// protected RegisterRelativeSymbol() { - _variableType = new LazyVariable(GetVariableType); - _name = new LazyVariable(GetName); + _variableType = new LazyVariable(x => x.GetVariableType()); + _name = new LazyVariable(x => x.GetName()); } /// @@ -28,10 +28,10 @@ protected RegisterRelativeSymbol() /// The type of variable the register+offset pair stores. public RegisterRelativeSymbol(Utf8String name, ushort baseRegister, int offset, CodeViewTypeRecord variableType) { - _name = new LazyVariable(name); + _name = new LazyVariable(name); BaseRegister = baseRegister; Offset = offset; - _variableType = new LazyVariable(variableType); + _variableType = new LazyVariable(variableType); } /// @@ -58,15 +58,15 @@ public int Offset /// public CodeViewTypeRecord? VariableType { - get => _variableType.Value; - set => _variableType.Value = value; + get => _variableType.GetValue(this); + set => _variableType.SetValue(value); } /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/RegisterSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/RegisterSymbol.cs index 9cf7bde0b..06cc40470 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/RegisterSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/RegisterSymbol.cs @@ -7,16 +7,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class RegisterSymbol : CodeViewSymbol, IVariableSymbol { - private readonly LazyVariable _variableType; - private readonly LazyVariable _name; + private readonly LazyVariable _variableType; + private readonly LazyVariable _name; /// /// Initializes an empty register variable symbol. /// protected RegisterSymbol() { - _variableType = new LazyVariable(GetVariableType); - _name = new LazyVariable(GetName); + _variableType = new LazyVariable(x => x.GetVariableType()); + _name = new LazyVariable(x => x.GetName()); } /// @@ -28,8 +28,8 @@ protected RegisterSymbol() public RegisterSymbol(Utf8String? name, CodeViewTypeRecord? variableType, ushort register) { Register = register; - _variableType = new LazyVariable(variableType); - _name = new LazyVariable(name); + _variableType = new LazyVariable(variableType); + _name = new LazyVariable(name); } /// @@ -47,15 +47,15 @@ public ushort Register /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// public CodeViewTypeRecord? VariableType { - get => _variableType.Value; - set => _variableType.Value = value; + get => _variableType.GetValue(this); + set => _variableType.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/SectionSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/SectionSymbol.cs index a6885e204..5103a6ca1 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/SectionSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/SectionSymbol.cs @@ -7,14 +7,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class SectionSymbol : CodeViewSymbol { - private readonly LazyVariable _name; + private readonly LazyVariable _name; /// /// Initializes an empty section symbol. /// protected SectionSymbol() { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -23,7 +23,7 @@ protected SectionSymbol() /// The name of the section. public SectionSymbol(Utf8String name) { - _name = new LazyVariable(name); + _name = new LazyVariable(name); } /// @@ -82,8 +82,8 @@ public SectionFlags Attributes /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/ThunkSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ThunkSymbol.cs index e765b660e..a13549893 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/ThunkSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/ThunkSymbol.cs @@ -8,7 +8,7 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class ThunkSymbol : CodeViewSymbol, IScopeCodeViewSymbol { - private readonly LazyVariable _name; + private readonly LazyVariable _name; private IList? _symbols; @@ -17,7 +17,7 @@ public class ThunkSymbol : CodeViewSymbol, IScopeCodeViewSymbol /// protected ThunkSymbol() { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -27,7 +27,7 @@ protected ThunkSymbol() /// The size of the thunk in bytes. public ThunkSymbol(Utf8String name, ushort size) { - _name = new LazyVariable(name); + _name = new LazyVariable(name); Size = size; } @@ -86,8 +86,8 @@ public ThunkOrdinal Ordinal /// public Utf8String? Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs index d7f4e8e10..f7873ce90 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -7,16 +7,16 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class UserDefinedTypeSymbol : CodeViewSymbol { - private readonly LazyVariable _name; - private readonly LazyVariable _type; + 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); + _name = new LazyVariable(x => x.GetName()); + _type = new LazyVariable(x => x.GetSymbolType()); } /// @@ -26,8 +26,8 @@ protected UserDefinedTypeSymbol() /// The type. public UserDefinedTypeSymbol(Utf8String name, CodeViewTypeRecord type) { - _name = new LazyVariable(name); - _type = new LazyVariable(type); + _name = new LazyVariable(name); + _type = new LazyVariable(type); } /// @@ -38,8 +38,8 @@ public UserDefinedTypeSymbol(Utf8String name, CodeViewTypeRecord type) /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// @@ -47,8 +47,8 @@ public Utf8String Name /// public CodeViewTypeRecord Type { - get => _type.Value; - set => _type.Value = value; + get => _type.GetValue(this); + set => _type.SetValue(value); } /// diff --git a/src/AsmResolver.Symbols.Pdb/Records/UsingNamespaceSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UsingNamespaceSymbol.cs index 05badd80c..66b0e68f0 100644 --- a/src/AsmResolver.Symbols.Pdb/Records/UsingNamespaceSymbol.cs +++ b/src/AsmResolver.Symbols.Pdb/Records/UsingNamespaceSymbol.cs @@ -5,14 +5,14 @@ namespace AsmResolver.Symbols.Pdb.Records; /// public class UsingNamespaceSymbol : CodeViewSymbol { - private readonly LazyVariable _name; + private readonly LazyVariable _name; /// /// Initializes a new empty using namespace. /// protected UsingNamespaceSymbol() { - _name = new LazyVariable(GetName); + _name = new LazyVariable(x => x.GetName()); } /// @@ -21,7 +21,7 @@ protected UsingNamespaceSymbol() /// The namespace to use. public UsingNamespaceSymbol(Utf8String name) { - _name = new LazyVariable(name); + _name = new LazyVariable(name); } /// @@ -32,8 +32,8 @@ public UsingNamespaceSymbol(Utf8String name) /// public Utf8String Name { - get => _name.Value; - set => _name.Value = value; + get => _name.GetValue(this); + set => _name.SetValue(value); } /// diff --git a/src/AsmResolver/LazyVariable.cs b/src/AsmResolver/LazyVariable.cs index 6b4ac9814..a3fe78dd8 100644 --- a/src/AsmResolver/LazyVariable.cs +++ b/src/AsmResolver/LazyVariable.cs @@ -77,9 +77,17 @@ private void InitializeValue() } } } - } + /// + /// Represents a variable that can be lazily initialized and/or assigned a new value. + /// + /// The type of the owner of the variable. + /// The type of the values that the variable stores. + /// + /// For performance reasons, this class locks on itself for thread synchronization. Therefore, consumers + /// should not lock instances of this class as a lock object to avoid dead-locks. + /// public sealed class LazyVariable { private TValue? _value; @@ -114,6 +122,11 @@ public bool IsInitialized private set; } + /// + /// Obtains the value stored in the variable, initializing the variable if necessary. + /// + /// The owner of the variable. + /// The value. public TValue GetValue(TOwner owner) { if (!IsInitialized) @@ -121,6 +134,10 @@ public TValue GetValue(TOwner owner) return _value!; } + /// + /// Assigns a new value to the variable. + /// + /// The new value. public void SetValue(TValue value) { lock (this) @@ -141,7 +158,5 @@ private void InitializeValue(TOwner owner) } } } - } - } From 6011f7e9329a43e99d6b30700254b346cf275f65 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> Date: Wed, 26 Apr 2023 17:16:17 -0400 Subject: [PATCH 04/26] Replace uses of DiagnosticBag with IErrorListener where feasible in the build process --- .../Builder/DotNetDirectoryFactory.cs | 8 ++-- .../Builder/IDotNetDirectoryFactory.cs | 6 +-- .../Builder/ManagedPEImageBuilder.cs | 10 ++--- .../Builder/PEImageBuildContext.cs | 37 ++++++++++++++----- .../Builder/PEImageBuildResult.cs | 31 ++++++++++++++-- src/AsmResolver.DotNet/ModuleDefinition.cs | 10 +++-- .../TokenPreservationTestBase.cs | 5 ++- 7 files changed, 78 insertions(+), 29 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs index be0f359bd..7f2e56455 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs @@ -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 @@ -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) diff --git a/src/AsmResolver.DotNet/Builder/IDotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/IDotNetDirectoryFactory.cs index ce0df8d2f..97ba5fd96 100644 --- a/src/AsmResolver.DotNet/Builder/IDotNetDirectoryFactory.cs +++ b/src/AsmResolver.DotNet/Builder/IDotNetDirectoryFactory.cs @@ -13,9 +13,9 @@ public interface IDotNetDirectoryFactory /// /// The module to serialize to a .NET data directory. /// The object responsible for providing references to native symbols. - /// The bag that is used to collect all diagnostic information during the building process. + /// The listener that is used to collect all diagnostic information during the building process. /// The serialized data directory. /// Occurs when the metadata builder encounters an error. - DotNetDirectoryBuildResult CreateDotNetDirectory(ModuleDefinition module, INativeSymbolsProvider symbolsProvider, DiagnosticBag diagnosticBag); + DotNetDirectoryBuildResult CreateDotNetDirectory(ModuleDefinition module, INativeSymbolsProvider symbolsProvider, IErrorListener errorListener); } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs index c67b17956..e16ff3478 100644 --- a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs +++ b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using AsmResolver.DotNet.Code.Native; using AsmResolver.PE; @@ -74,7 +74,7 @@ public PEImageBuildResult CreateImage(ModuleDefinition module) var result = DotNetDirectoryFactory.CreateDotNetDirectory( module, symbolProvider, - context.DiagnosticBag); + context.ErrorListener); image.DotNetDirectory = result.Directory; tokenMapping = result.TokenMapping; @@ -108,12 +108,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); } } } diff --git a/src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs b/src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs index 4947ae819..aa41ef1e9 100644 --- a/src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs +++ b/src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs @@ -12,24 +12,43 @@ public class PEImageBuildContext /// public PEImageBuildContext() { - DiagnosticBag = new DiagnosticBag(); - } - + ErrorListener = new DiagnosticBag(); + } + /// /// Creates a new build context. /// - /// The diagnostic bag to use. + /// The diagnostic bag to use. + [Obsolete] public PEImageBuildContext(DiagnosticBag diagnosticBag) { - DiagnosticBag = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag)); - } - + ErrorListener = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag)); + } + /// - /// Gets the bag that collects all diagnostic information during the building process. + /// Creates a new build context. /// + /// The diagnostic bag to use. + public PEImageBuildContext(IErrorListener errorListener) + { + ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); + } + + /// + /// Gets the bag that collects all diagnostic information during the building process. + /// + [Obsolete] public DiagnosticBag DiagnosticBag + { + get => (DiagnosticBag)ErrorListener; + } + + /// + /// Gets the error listener that handles all diagnostic information during the building process. + /// + public IErrorListener ErrorListener { get; } } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs index 95aef83eb..bf7daad24 100644 --- a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs +++ b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs @@ -14,11 +14,25 @@ public class PEImageBuildResult /// /// The constructed image, or null if the construction failed. /// The diagnostics that were collected during the construction of the image. - /// An object that maps metadata members to their newly assigned tokens. + /// An object that maps metadata members to their newly assigned tokens. + [Obsolete] public PEImageBuildResult(IPEImage? image, DiagnosticBag diagnosticBag, ITokenMapping tokenMapping) { ConstructedImage = image; - DiagnosticBag = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag)); + ErrorListener = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag)); + TokenMapping = tokenMapping ?? throw new ArgumentNullException(nameof(tokenMapping)); + } + + /// + /// Creates a new instance of the class. + /// + /// The constructed image, or null if the construction failed. + /// The diagnostics that were collected during the construction of the image. + /// An object that maps metadata members to their newly assigned tokens. + public PEImageBuildResult(IPEImage? image, IErrorListener errorListener, ITokenMapping tokenMapping) + { + ConstructedImage = image; + ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); TokenMapping = tokenMapping ?? throw new ArgumentNullException(nameof(tokenMapping)); } @@ -34,12 +48,21 @@ public IPEImage? ConstructedImage /// Gets a value indicating whether the image was constructed successfully or not. /// [MemberNotNullWhen(false, nameof(ConstructedImage))] - public bool HasFailed => DiagnosticBag.IsFatal; + public bool HasFailed => ConstructedImage is null; /// /// Gets the bag containing the diagnostics that were collected during the construction of the image. - /// + /// + [Obsolete] public DiagnosticBag DiagnosticBag + { + get=> (DiagnosticBag)ErrorListener; + } + + /// + /// Gets the error listener handling the diagnostics that were collected during the construction of the image. + /// + public IErrorListener ErrorListener { get; } diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 710745aa4..ddbe0e298 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -1295,14 +1295,18 @@ public void Write(IBinaryStreamWriter writer, IPEImageBuilder imageBuilder, IPEF public IPEImage ToPEImage(IPEImageBuilder imageBuilder) { var result = imageBuilder.CreateImage(this); - if (result.DiagnosticBag.HasErrors) + if (result.ErrorListener is DiagnosticBag diagnosticBag && diagnosticBag.HasErrors) { throw new AggregateException( "Construction of the PE image failed with one or more errors.", - result.DiagnosticBag.Exceptions); + diagnosticBag.Exceptions); + } + else if (result.HasFailed) + { + throw new Exception("Construction of the PE image failed."); } - return result.ConstructedImage!; + return result.ConstructedImage; } } } diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs index 3adf1bd7e..ff2046e00 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs @@ -39,7 +39,10 @@ protected static ModuleDefinition RebuildAndReloadModule(ModuleDefinition module var result = builder.CreateImage(module); if (result.HasFailed) - throw new AggregateException(result.DiagnosticBag.Exceptions); + if (result.ErrorListener is DiagnosticBag diagnosticBag) + throw new AggregateException(diagnosticBag.Exceptions); + else + throw new Exception("Image creation failed."); var newImage = result.ConstructedImage; From 69422683caad139ba33c782aaa3678cebfddaa53 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 27 Apr 2023 17:19:17 +0200 Subject: [PATCH 05/26] Add signature comparer versioning test. --- .../Signatures/SignatureComparerTest.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs index bbe5cd367..5fe70cd12 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs @@ -165,6 +165,29 @@ public void MatchForwardedNestedTypes() Assert.NotEqual(type2, resolvedType1, _comparer); // Fails } + [Fact] + public void AssemblyHashCodeStrict() + { + var assembly1 = new AssemblyReference("SomeAssembly", new Version(1, 2, 3, 4)); + var assembly2 = new AssemblyReference("SomeAssembly", new Version(1, 2, 3, 4)); + + Assert.Equal( + _comparer.GetHashCode((AssemblyDescriptor) assembly1), + _comparer.GetHashCode((AssemblyDescriptor) assembly2)); + } + + [Fact] + public void AssemblyHashCodeVersionAgnostic() + { + var assembly1 = new AssemblyReference("SomeAssembly", new Version(1, 2, 3, 4)); + var assembly2 = new AssemblyReference("SomeAssembly", new Version(5, 6, 7, 8)); + + var comparer = new SignatureComparer(SignatureComparisonFlags.VersionAgnostic); + Assert.Equal( + comparer.GetHashCode((AssemblyDescriptor) assembly1), + comparer.GetHashCode((AssemblyDescriptor) assembly2)); + } + private class NestedTypes { public class FirstType From 3f20b0df8e537b25805c45c6abea03776c933d88 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 27 Apr 2023 17:25:39 +0200 Subject: [PATCH 06/26] BUGGIX: Do not include version in hashcode computation when relaxing version number comparisons. --- .../SignatureComparer.ResolutionScope.cs | 5 ++++- .../Signatures/SignatureComparerTest.cs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs index 31fdb7eac..b2306bdfb 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs @@ -92,7 +92,10 @@ public int GetHashCode(AssemblyDescriptor obj) int hashCode = obj.Name is null ? 0 : obj.Name.GetHashCode(); hashCode = (hashCode * 397) ^ (obj.Culture is not null ? obj.Culture.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (int) TableIndex.AssemblyRef; - hashCode = (hashCode * 397) ^ obj.Version.GetHashCode(); + + if (!AcceptNewerAssemblyVersionNumbers && !AcceptOlderAssemblyVersionNumbers) + hashCode = (hashCode * 397) ^ obj.Version.GetHashCode(); + hashCode = (hashCode * 397) ^ (int) obj.Attributes; byte[]? publicKeyToken = obj.GetPublicKeyToken(); diff --git a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs index 5fe70cd12..34d4345e2 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; @@ -188,6 +189,22 @@ public void AssemblyHashCodeVersionAgnostic() comparer.GetHashCode((AssemblyDescriptor) assembly2)); } + [Fact] + public void CorlibComparison() + { + // https://github.com/Washi1337/AsmResolver/issues/427 + + var comparer = new SignatureComparer(SignatureComparisonFlags.VersionAgnostic); + + var reference1 = KnownCorLibs.SystemRuntime_v5_0_0_0; + var reference2 = KnownCorLibs.SystemRuntime_v6_0_0_0; + Assert.Equal(reference1, reference2, comparer); + + var set = new HashSet(comparer); + Assert.True(set.Add(reference1)); + Assert.False(set.Add(reference2)); + } + private class NestedTypes { public class FirstType From 64acd1c48b71ba81027186ca5567ca249034359e Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 27 Apr 2023 17:26:54 +0200 Subject: [PATCH 07/26] BUGFIX: Skip Windows specific tests on Unix platforms. --- test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs | 6 +++--- .../Signatures/SignatureComparerTest.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 1f84d6daa..a4f8b60cd 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -205,21 +205,21 @@ public void SameManifestContentsShouldResultInSameBundleID() Assert.Equal(manifest.BundleID, newManifest.GenerateDeterministicBundleID()); } - [Fact] + [SkippableFact] public void PatchAndRepackageExistingBundleV1() { Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); AssertPatchAndRepackageChangesOutput(Properties.Resources.HelloWorld_SingleFile_V1); } - [Fact] + [SkippableFact] public void PatchAndRepackageExistingBundleV2() { Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); AssertPatchAndRepackageChangesOutput(Properties.Resources.HelloWorld_SingleFile_V2); } - [Fact] + [SkippableFact] public void PatchAndRepackageExistingBundleV6() { Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); diff --git a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs index 34d4345e2..aed57cede 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs @@ -193,7 +193,7 @@ public void AssemblyHashCodeVersionAgnostic() public void CorlibComparison() { // https://github.com/Washi1337/AsmResolver/issues/427 - + var comparer = new SignatureComparer(SignatureComparisonFlags.VersionAgnostic); var reference1 = KnownCorLibs.SystemRuntime_v5_0_0_0; From 080442fe040ee55800fffa9625265fce800db294 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 29 Apr 2023 12:02:42 +0200 Subject: [PATCH 08/26] Add ModuleDefinition::IsLoadedAs32Bit, add Platform::Is32Bit and ::PointerSize. --- src/AsmResolver.DotNet/ModuleDefinition.cs | 24 ++++++++ .../DotNet/DotNetDirectoryFlagsExtensions.cs | 58 +++++++++++++++++++ src/AsmResolver.PE/Platforms/Amd64Platform.cs | 3 + src/AsmResolver.PE/Platforms/I386Platform.cs | 3 + src/AsmResolver.PE/Platforms/Platform.cs | 18 ++++++ .../ModuleDefinitionTest.cs | 49 ++++++++++++++++ 6 files changed, 155 insertions(+) create mode 100644 src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 710745aa4..72cf9497a 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -19,6 +19,7 @@ using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.File; using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Platforms; using AsmResolver.PE.Win32Resources; namespace AsmResolver.DotNet @@ -789,6 +790,29 @@ public ReferenceImporter DefaultImporter } } + /// + /// Determines whether the module is loaded as a 32-bit process. + /// + /// + /// true if the module is loaded as a 32-bit process, false if it is loaded as a 64-bit process. + /// + public bool IsLoadedAs32Bit() => IsLoadedAs32Bit(false, true); + + /// + /// Determines whether the module is loaded as a 32-bit process. + /// + /// true if a 32-bit system should be assumed. + /// true if a 32-bit load is preferred. + /// + /// true if the module is loaded as a 32-bit process, false if it is loaded as a 64-bit process. + /// + public bool IsLoadedAs32Bit(bool assume32BitSystem, bool prefer32Bit) + { + // Assume 32-bit if platform is unknown. + return Platform.TryGet(MachineType, out var platform) + && Attributes.IsLoadedAs32Bit(platform, assume32BitSystem, prefer32Bit); + } + /// /// Looks up a member by its metadata token. /// diff --git a/src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs b/src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs new file mode 100644 index 000000000..94c80c7e8 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs @@ -0,0 +1,58 @@ +using AsmResolver.PE.Platforms; + +namespace AsmResolver.PE.DotNet +{ + /// + /// Provides extension methods for . + /// + public static class DotNetDirectoryFlagsExtensions + { + /// + /// Determines whether the module is loaded as a 32-bit process. + /// + /// + /// The flags of the module as specified in its COR20 header. + /// The platform to assume the module is loaded on. + /// true if the module is loaded as a 32-bit process, false if it is loaded as a 64-bit process. + /// + public static bool IsLoadedAs32Bit(this DotNetDirectoryFlags flags, Platform platform) + { + return flags.IsLoadedAs32Bit(platform, false, true); + } + + /// + /// Determines whether the module is loaded as a 32-bit process. + /// + /// The flags of the module as specified in its COR20 header. + /// The platform to assume the module is loaded on. + /// true if a 32-bit system should be assumed. + /// true if a 32-bit load is preferred. + /// + /// true if the module is loaded as a 32-bit process, false if it is loaded as a 64-bit process. + /// + public static bool IsLoadedAs32Bit(this DotNetDirectoryFlags flags, Platform platform, bool assume32BitSystem, bool prefer32Bit) + { + // Short-circuit all 64-bit platforms. + if (!platform.Is32Bit) + return false; + + // Check if we are dealing with an AnyCPU binary. + if (platform is not I386Platform) + return true; + + // Non-ILOnly 32-bit binaries are always loaded as 32-bit. + if ((flags & DotNetDirectoryFlags.ILOnly) == 0) + return true; + + // If we require 32-bit as specified by COR20 headers, load as 32-bit. + if ((flags & DotNetDirectoryFlags.Bit32Required) != 0) + return true; + + // Try cater to preference. + if ((flags & DotNetDirectoryFlags.Bit32Preferred) != 0) + return assume32BitSystem | prefer32Bit; + + return assume32BitSystem; + } + } +} diff --git a/src/AsmResolver.PE/Platforms/Amd64Platform.cs b/src/AsmResolver.PE/Platforms/Amd64Platform.cs index 08c042736..2296bdb7d 100644 --- a/src/AsmResolver.PE/Platforms/Amd64Platform.cs +++ b/src/AsmResolver.PE/Platforms/Amd64Platform.cs @@ -24,6 +24,9 @@ public static Amd64Platform Instance /// public override bool IsClrBootstrapperRequired => false; + /// + public override bool Is32Bit => false; + /// public override RelocatableSegment CreateThunkStub(ISymbol entryPoint) { diff --git a/src/AsmResolver.PE/Platforms/I386Platform.cs b/src/AsmResolver.PE/Platforms/I386Platform.cs index 26cb29d78..ae7f6dc61 100644 --- a/src/AsmResolver.PE/Platforms/I386Platform.cs +++ b/src/AsmResolver.PE/Platforms/I386Platform.cs @@ -24,6 +24,9 @@ public static I386Platform Instance /// public override bool IsClrBootstrapperRequired => true; + /// + public override bool Is32Bit => true; + /// public override RelocatableSegment CreateThunkStub(ISymbol entryPoint) { diff --git a/src/AsmResolver.PE/Platforms/Platform.cs b/src/AsmResolver.PE/Platforms/Platform.cs index 151b4da02..288416774 100644 --- a/src/AsmResolver.PE/Platforms/Platform.cs +++ b/src/AsmResolver.PE/Platforms/Platform.cs @@ -66,6 +66,24 @@ public abstract bool IsClrBootstrapperRequired get; } + /// + /// Gets a value indicating whether the platform is a 32-bit platform. + /// + public abstract bool Is32Bit + { + get; + } + + /// + /// Gets a value indicating whether the platform is a 64-bit platform. + /// + public bool Is64Bit => !Is32Bit; + + /// + /// Gets a value indicating the size of a single pointer. + /// + public int PointerSize => Is32Bit ? sizeof(uint) : sizeof(ulong); + /// /// Creates a new thunk stub that transfers control to the provided symbol. /// diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index db9a460ae..b59b4636b 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -9,6 +9,7 @@ using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.File.Headers; using AsmResolver.PE.Win32Resources; using Xunit; using FileAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.FileAttributes; @@ -517,5 +518,53 @@ public void GetModuleTypeLookalikeNet6() Assert.NotNull(type); Assert.Same(type, module.GetModuleType()); } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, true, false)] + [InlineData(true, false, true)] + [InlineData(true, true, true)] + public void IsLoadedAs32BitAnyCPUModule(bool assume32Bit, bool prefer32Bit, bool expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(expected, module.IsLoadedAs32Bit(assume32Bit, prefer32Bit)); + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, true, true)] + [InlineData(true, false, true)] + [InlineData(true, true, true)] + public void IsLoadedAs32BitAnyCPUModulePrefer32Bit(bool assume32Bit, bool prefer32Bit, bool expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + module.IsBit32Preferred = true; + Assert.Equal(expected, module.IsLoadedAs32Bit(assume32Bit, prefer32Bit)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void IsLoadedAs32Bit64BitModule(bool assume32Bit, bool prefer32Bit) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + module.MachineType = MachineType.Amd64; + Assert.False(module.IsLoadedAs32Bit(assume32Bit, prefer32Bit)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void IsLoadedAs32Bit32BitModule(bool assume32Bit, bool prefer32Bit) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + module.MachineType = MachineType.I386; + module.IsBit32Required = true; + Assert.True(module.IsLoadedAs32Bit(assume32Bit, prefer32Bit)); + } } } From abd981f5fa5974c75724913315ffb50c6f7fd343 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 29 Apr 2023 13:11:24 +0200 Subject: [PATCH 09/26] Add ZeroesSegment, add support for reading fieldrva data from virtual segments. --- .../Serialized/SerializedFieldDefinition.cs | 16 ++++- src/AsmResolver.PE.File/PESection.cs | 4 +- src/AsmResolver.PE.File/PESegmentReference.cs | 7 ++- .../DotNet/Builder/ManagedPEFileBuilder.cs | 17 ++--- .../DotNet/Metadata/FieldRvaDataReader.cs | 59 ++++++++++++------ .../DotNet/Metadata/IFieldRvaDataReader.cs | 7 ++- src/AsmResolver/IO/ZeroesDataSource.cs | 58 +++++++++++++++++ src/AsmResolver/ZeroesSegment.cs | 54 ++++++++++++++++ .../FieldDefinitionTest.cs | 14 ++++- .../Properties/Resources.Designer.cs | 7 +++ .../Properties/Resources.resx | 3 + .../Resources/HelloWorld.VirtualSegment.exe | Bin 0 -> 5632 bytes .../DotNet/Metadata/FieldRvaDataReaderTest.cs | 13 ++-- 13 files changed, 220 insertions(+), 39 deletions(-) create mode 100644 src/AsmResolver/IO/ZeroesDataSource.cs create mode 100644 src/AsmResolver/ZeroesSegment.cs create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.VirtualSegment.exe diff --git a/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs index 768f687fd..2fb0adfce 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs @@ -7,6 +7,7 @@ using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; +using AsmResolver.PE.Platforms; namespace AsmResolver.DotNet.Serialized { @@ -84,15 +85,24 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke protected override ISegment? GetFieldRva() { var module = _context.ParentModule; + if (!Platform.TryGet(module.MachineType, out var platform)) + return null; uint rid = module.GetFieldRvaRid(MetadataToken); bool result = _context.TablesStream .GetTable(TableIndex.FieldRva) .TryGetByRid(rid, out var fieldRvaRow); - return result - ? _context.Parameters.FieldRvaDataReader.ResolveFieldData(_context, _context.Metadata, fieldRvaRow) - : null; + if (result) + { + return _context.Parameters.FieldRvaDataReader.ResolveFieldData( + _context, + platform, + _context.ParentModule.DotNetDirectory, + fieldRvaRow); + } + + return null; } /// diff --git a/src/AsmResolver.PE.File/PESection.cs b/src/AsmResolver.PE.File/PESection.cs index b091780ae..a0026d7c8 100644 --- a/src/AsmResolver.PE.File/PESection.cs +++ b/src/AsmResolver.PE.File/PESection.cs @@ -204,7 +204,9 @@ public ISegment? Contents /// Gets a value indicating whether the section is readable using a binary stream reader. /// [MemberNotNullWhen(true, nameof(Contents))] - public bool IsReadable => Contents is IReadableSegment; + public bool IsReadable => Contents is VirtualSegment segment + ? segment.IsReadable + : Contents is IReadableSegment; /// public ulong Offset => Contents?.Offset ?? 0; diff --git a/src/AsmResolver.PE.File/PESegmentReference.cs b/src/AsmResolver.PE.File/PESegmentReference.cs index f05889f7a..40503e938 100644 --- a/src/AsmResolver.PE.File/PESegmentReference.cs +++ b/src/AsmResolver.PE.File/PESegmentReference.cs @@ -33,11 +33,16 @@ public uint Rva } /// - public bool CanRead => _peFile.TryGetSectionContainingRva(Rva, out _); + public bool CanRead => _peFile.TryGetSectionContainingRva(Rva, out var section) && section.IsReadable; /// public bool IsBounded => false; + /// + /// Gets a value indicating whether the reference points to a valid section within the PE file. + /// + public bool IsValidAddress => _peFile.TryGetSectionContainingRva(Rva, out _); + /// public BinaryStreamReader CreateReader() => _peFile.CreateReaderAtRva(Rva); diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs index 458daad75..8475d8805 100644 --- a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs +++ b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using AsmResolver.PE.Builder; -using AsmResolver.PE.Code; -using AsmResolver.PE.Debug; using AsmResolver.PE.Debug.Builder; using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata; @@ -495,10 +493,10 @@ private static void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesSt private static void AddFieldRvasToTable(ManagedPEBuilderContext context) { - var metadata = context.DotNetSegment.DotNetDirectory.Metadata; - var fieldRvaTable = metadata - !.GetStream() - !.GetTable(TableIndex.FieldRva); + var directory = context.DotNetSegment.DotNetDirectory; + var fieldRvaTable = directory.Metadata! + .GetStream() + .GetTable(TableIndex.FieldRva); if (fieldRvaTable.Count == 0) return; @@ -508,7 +506,12 @@ private static void AddFieldRvasToTable(ManagedPEBuilderContext context) for (int i = 0; i < fieldRvaTable.Count; i++) { - var data = reader.ResolveFieldData(ThrowErrorListener.Instance, metadata, fieldRvaTable[i]); + var data = reader.ResolveFieldData( + ThrowErrorListener.Instance, + context.Platform, + directory, + fieldRvaTable[i]); + if (data is null) continue; diff --git a/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs b/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs index cb9ea25f2..b0360a781 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs @@ -3,6 +3,8 @@ using AsmResolver.PE.DotNet.Metadata.Blob; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; +using AsmResolver.PE.File; +using AsmResolver.PE.Platforms; namespace AsmResolver.PE.DotNet.Metadata { @@ -12,40 +14,59 @@ namespace AsmResolver.PE.DotNet.Metadata public class FieldRvaDataReader : IFieldRvaDataReader { /// - public ISegment? ResolveFieldData(IErrorListener listener, IMetadata metadata, in FieldRvaRow fieldRvaRow) + public ISegment? ResolveFieldData( + IErrorListener listener, + Platform platform, + IDotNetDirectory directory, + in FieldRvaRow fieldRvaRow) { if (fieldRvaRow.Data.IsBounded) return fieldRvaRow.Data.GetSegment(); - if (fieldRvaRow.Data.CanRead) + var metadata = directory.Metadata; + if (metadata is null) + { + listener.BadImage(".NET directory does not contain a metadata directory."); + return null; + } + + if (!metadata.TryGetStream(out var tablesStream)) { - if (!metadata.TryGetStream(out var tablesStream)) - { - listener.BadImage("Metadata does not contain a tables stream."); - return null; - } + listener.BadImage("Metadata does not contain a tables stream."); + return null; + } - var table = tablesStream.GetTable(TableIndex.Field); - if (fieldRvaRow.Field > table.Count) - { - listener.BadImage("FieldRva row has an invalid Field column value."); - return null; - } + var table = tablesStream.GetTable(TableIndex.Field); + if (fieldRvaRow.Field > table.Count) + { + listener.BadImage("FieldRva row has an invalid Field column value."); + return null; + } - var field = table.GetByRid(fieldRvaRow.Field); - int valueSize = DetermineFieldSize(metadata, field); + var field = table.GetByRid(fieldRvaRow.Field); + int valueSize = DetermineFieldSize(directory, field); + if (fieldRvaRow.Data.CanRead) + { var reader = fieldRvaRow.Data.CreateReader(); return DataSegment.FromReader(ref reader, valueSize); } + if (fieldRvaRow.Data is PESegmentReference {IsValidAddress: true}) + { + // We are reading from a virtual segment that is resized at runtime, assume zeroes. + var segment = new ZeroesSegment((uint) valueSize); + segment.UpdateOffsets(new RelocationParameters(fieldRvaRow.Data.Offset, fieldRvaRow.Data.Rva)); + return segment; + } + listener.NotSupported("FieldRva row has an invalid or unsupported data column."); return null; } - private int DetermineFieldSize(IMetadata metadata, in FieldDefinitionRow field) + private int DetermineFieldSize(IDotNetDirectory directory, in FieldDefinitionRow field) { - if (!metadata.TryGetStream(out var blobStream) + if (!directory.Metadata!.TryGetStream(out var blobStream) || !blobStream.TryGetBlobReaderByIndex(field.Signature, out var reader)) { return 0; @@ -67,8 +88,8 @@ private int DetermineFieldSize(IMetadata metadata, in FieldDefinitionRow field) ElementType.U8 => sizeof(ulong), ElementType.R4 => sizeof(float), ElementType.R8 => sizeof(double), - ElementType.ValueType => GetCustomTypeSize(metadata, ref reader), - ElementType.Class => GetCustomTypeSize(metadata, ref reader), + ElementType.ValueType => GetCustomTypeSize(directory.Metadata, ref reader), + ElementType.Class => GetCustomTypeSize(directory.Metadata, ref reader), _ => throw new ArgumentOutOfRangeException() }; } diff --git a/src/AsmResolver.PE/DotNet/Metadata/IFieldRvaDataReader.cs b/src/AsmResolver.PE/DotNet/Metadata/IFieldRvaDataReader.cs index ea54ebb13..f812881e9 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/IFieldRvaDataReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/IFieldRvaDataReader.cs @@ -1,4 +1,5 @@ using AsmResolver.PE.DotNet.Metadata.Tables.Rows; +using AsmResolver.PE.Platforms; namespace AsmResolver.PE.DotNet.Metadata { @@ -11,9 +12,11 @@ public interface IFieldRvaDataReader /// Reads a data segment referenced by a row in the FieldRVA table. /// /// The object responsible for recording parser errors. - /// The metadata directory to read from. + /// + /// The .NET directory to read from. /// The row referencing the data. /// The data segment, or null if no data was referenced. - ISegment? ResolveFieldData(IErrorListener listener, IMetadata metadata, in FieldRvaRow fieldRvaRow); + ISegment? ResolveFieldData(IErrorListener listener, Platform platform, IDotNetDirectory directory, + in FieldRvaRow fieldRvaRow); } } diff --git a/src/AsmResolver/IO/ZeroesDataSource.cs b/src/AsmResolver/IO/ZeroesDataSource.cs new file mode 100644 index 000000000..1e15c006e --- /dev/null +++ b/src/AsmResolver/IO/ZeroesDataSource.cs @@ -0,0 +1,58 @@ +using System; + +namespace AsmResolver.IO +{ + /// + /// Implements a data source that reads zero bytes. + /// + public sealed class ZeroesDataSource : IDataSource + { + /// + /// Creates a new zeroes data source. + /// + /// The number of zero bytes. + public ZeroesDataSource(ulong length) + : this(0, length) + { + } + + /// + /// Creates a new zeroes data source. + /// + /// The base address of the segment. + /// The number of zero bytes. + public ZeroesDataSource(ulong baseAddress, ulong length) + { + BaseAddress = baseAddress; + Length = length; + } + + /// + public ulong BaseAddress + { + get; + } + + /// + public byte this[ulong address] => !IsValidAddress(address) + ? throw new IndexOutOfRangeException(nameof(address)) + : (byte) 0; + + /// + public ulong Length + { + get; + } + + /// + public bool IsValidAddress(ulong address) => address - BaseAddress < Length; + + /// + public int ReadBytes(ulong address, byte[] buffer, int index, int count) + { + int actualLength = (int) Math.Min(Length, (ulong) count); + Array.Clear(buffer, index, count); + return actualLength; + } + } +} diff --git a/src/AsmResolver/ZeroesSegment.cs b/src/AsmResolver/ZeroesSegment.cs new file mode 100644 index 000000000..8a3da32c1 --- /dev/null +++ b/src/AsmResolver/ZeroesSegment.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; +using AsmResolver.IO; + +namespace AsmResolver +{ + /// + /// Represents a segment containing zero bytes. + /// + public class ZeroesSegment : SegmentBase, IReadableSegment + { + private ZeroesDataSource? _dataSource; + + /// + /// Creates a new zeroes-filled segment. + /// + /// The number of zero bytes. + public ZeroesSegment(uint size) + { + Size = size; + } + + /// + /// Gets the number of zero bytes that are stored in this segment. + /// + public uint Size + { + get; + } + + /// + public override uint GetPhysicalSize() => Size; + + /// + public override void Write(IBinaryStreamWriter writer) => writer.WriteZeroes((int) Size); + + /// + public BinaryStreamReader CreateReader(ulong fileOffset, uint size) + { + if (fileOffset < Offset || fileOffset > Offset + Size) + throw new ArgumentOutOfRangeException(nameof(fileOffset)); + if (fileOffset + size > Offset + Size) + throw new EndOfStreamException(); + + _dataSource ??= new ZeroesDataSource(Offset, Size); + + return new BinaryStreamReader( + _dataSource, + fileOffset, + (uint) (fileOffset - Offset + Rva), + size); + } + } +} diff --git a/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs index ffaf57fe7..d1b61cbff 100644 --- a/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs @@ -134,11 +134,21 @@ public void ReadInvalidFieldRva() { var module = ModuleDefinition.FromBytes(Properties.Resources.FieldRvaTest); Assert.Throws(() => - module.GetModuleType().Fields.First(f => f.Name == "InvalidFieldRva").FieldRva); + module.GetModuleType()!.Fields.First(f => f.Name == "InvalidFieldRva").FieldRva); module = ModuleDefinition.FromBytes(Properties.Resources.FieldRvaTest, new ModuleReaderParameters(EmptyErrorListener.Instance)); - Assert.Null(module.GetModuleType().Fields.First(f => f.Name == "InvalidFieldRva").FieldRva); + Assert.Null(module.GetModuleType()!.Fields.First(f => f.Name == "InvalidFieldRva").FieldRva); + } + + [Fact] + public void ReadVirtualFieldRva() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_VirtualSegment); + var data = module.GetModuleType()!.Fields.First(f => f.Name == "__dummy__").FieldRva; + var readableData = Assert.IsAssignableFrom(data); + Assert.Equal(new byte[4], readableData.ToArray()); + Assert.Equal(new byte[4], data.WriteIntoArray()); } [Theory] diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index f6b0b4cf9..f3b7009ae 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -227,6 +227,13 @@ public static byte[] HelloWorld_DoubleUserStringsStream_EnC { } } + public static byte[] HelloWorld_VirtualSegment { + get { + object obj = ResourceManager.GetObject("HelloWorld_VirtualSegment", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] Assembly1_Forwarder { get { object obj = ResourceManager.GetObject("Assembly1_Forwarder", resourceCulture); diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index 06cc5cceb..44258e4d4 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -96,6 +96,9 @@ ..\Resources\HelloWorld.DoubleUserStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.VirtualSegment.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\Assembly1_Forwarder.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.VirtualSegment.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.VirtualSegment.exe new file mode 100644 index 0000000000000000000000000000000000000000..fadadabca4cf7bc01d22911600f75aff1e11b55a GIT binary patch literal 5632 zcmeHKU2I%O6+YwGj^kn{rJ;?XX(#K1rh!~qV$CNJUg?rB>=gr9QN(R3RQHJV1$nLg5WW1(o{HN=3>Gssu=V;Q{eL_|DwB zKX#Hf53STnIqNxd{^rb?Gjq?3=O28Ub`ViF+LbFrm(Vk0H2z?;3H9LJzZ#^U?|S3F zB{BELfx@~S%4*3M!6OG*YcPuiZ$F6O1{mz6Ov(0?#-otq|_6Fv0K?q}a@ z&UTqnbc;ySPkMxZ!lyU`&*EG&@Y(F59x6)^_zrlxMSR*o-8dKlMpX|3^&h#b z>oDB(A32+U;H+S806w4fF(Yk^rbmz6$C4g$fS-eU_%WO}jQBUx`6#fx3LioG4+_+s z=!eheX)g{WAKl^m&(CJj9|Zp{^Jg8ugq{H|#LDML?qcS@Vw7&zYoUQyrz3%l?m3?R zgysn2D03q2d+A@|d!m;P6XVNt3HZ2Re2QKcchK7~>m^02kPL~ZfPYVnAJh1GjY-QF zK*N3FpR*ybj)vHe^>=~3P4v?(l#^`p4E+%E3o2^v2m4+SJCpr*Puxmrx`TER%Z7mO zqyw717kHNL1O5h0YCNm)*ERm8#-_%$#s+->BMlm+BfwvxMc`3-2>7e?GVm1r0{Cn6 z2jD#Y1^8Q9ZUNIKucKRI>VW;Au^lZK>?|1L^M1MJsMEB%TCTZnV|A6>(DDPvF3~yV zIQ|OgGEMnj=sSv50y|Q3wx=koO0`PGEIDd2!kH-5B1My7sN9m%DA-Y_xL^hq70uxC zQIGn;BUjtc*p6COL1_El)yi3K%@15Ng3@%ZYlQ&U>`E=rc5Nq9zFV~&EnHHLxuL1B zt#3x03y=mYx>~^wOs~;aEY-Zoc9oV#cFA_^s6*RMnV~imUw!Jmhw&K*v43!fB+=E| z*Z0fs6qkp_E0q}MqOVUR_vu~yX%xC{UkS|Wg6~alSgOivD69wmqoL@5@vTF! z<}V!Q5$xdofP6OUph zXeV}CnW2iAV<>F+p`-}v(G17%zR7;% z8R*h?tHhz%2j4mLTc_YpkLctzsP7R3(?bPFJ_3|;-j2EG)44}Sgj>* zb&a84#vQZ@JWWfWW^r3BfS(1P0nPAQ_xt~gZ`bDFxId${@x9d4BqGX!5b$cEF3pgQ z+N9vqqcv12hJ(bnSpixT(hz!+BG~!hV)$A27-AQQDT0*^dF2|O_Gp_lZtnOuMkP>0 zCvf8n+_XNqRMm4dup$#2$_b!dJ=?XD<)`(yX2e-ohh*vbt6So`-uN^ssc6NyjPU_H zwAXbEw|%R?q8Cu(a(+haJdMDPFlcjIR^Tt}je@>66Yg{vYZm1p^76xxZ@`NGW6lPMWS zrdKu{-%}H*h6+<>P7m}C^pBg#<43}U7fz&Vfp;pj)|G39M_t(j)3eu5shhW+o)6BXY&n}PD?B%%#+I1)E=x%dSK&lzzA;&?I<}=BHilWPrbc29 zQBVsbemmV@JjYXd9Oe#{Rl_r{k?@cN>f1HMsmi&)uH&s%QQ-}G9c_A*fVLl8qE?Mx z2Xm^f9O*EgNSWcRSN9)LK}y!_Nek~7q;bu3LX|8^I~ln~G|kXQw!}U@(nGM;Pf&T*lug|Ff literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/FieldRvaDataReaderTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/FieldRvaDataReaderTest.cs index f0e130b99..3672038cf 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/FieldRvaDataReaderTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/FieldRvaDataReaderTest.cs @@ -4,6 +4,7 @@ using AsmResolver.PE.DotNet.Metadata; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; +using AsmResolver.PE.Platforms; using Xunit; namespace AsmResolver.PE.Tests.DotNet.Metadata @@ -46,7 +47,7 @@ public void ReadByteArray() { // Open image. var image = PEImage.FromFile(typeof(InitialValues).Assembly.Location); - var metadata = image.DotNetDirectory!.Metadata!; + var directory = image.DotNetDirectory!; // Get token of field. var cctorToken = (MetadataToken) typeof(InitialValues) @@ -57,14 +58,18 @@ public void ReadByteArray() !.MetadataToken; // Find associated field rva row. - var fieldRvaRow = FindFieldRvaRow(metadata.GetStream(), cctorToken, fieldToken); + var fieldRvaRow = FindFieldRvaRow(directory.Metadata!.GetStream(), cctorToken, fieldToken); // Read the data. var dataReader = new FieldRvaDataReader(); - var segment = dataReader.ResolveFieldData(ThrowErrorListener.Instance, metadata, fieldRvaRow) as IReadableSegment; + var segment = dataReader.ResolveFieldData( + ThrowErrorListener.Instance, + Platform.Get(image.MachineType), + directory, + fieldRvaRow) as IReadableSegment; Assert.NotNull(segment); - Assert.Equal(InitialValues.ByteArray, segment!.ToArray()); + Assert.Equal(InitialValues.ByteArray, segment.ToArray()); } } } From a6e4b24c8934f5816827771a9fbc1f6870559836 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 29 Apr 2023 13:22:58 +0200 Subject: [PATCH 10/26] Add read support for intptr fieldrva data. --- .../DotNet/Metadata/FieldRvaDataReader.cs | 7 +++++-- .../FieldDefinitionTest.cs | 18 ++++++++++++++++++ .../Properties/Resources.Designer.cs | 14 ++++++++++++++ .../Properties/Resources.resx | 6 ++++++ .../HelloWorld.IntPtrFieldRva.x64.exe | Bin 0 -> 4096 bytes .../HelloWorld.IntPtrFieldRva.x86.exe | Bin 0 -> 4608 bytes 6 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.IntPtrFieldRva.x64.exe create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.IntPtrFieldRva.x86.exe diff --git a/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs b/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs index b0360a781..07cb6d674 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs @@ -44,7 +44,7 @@ public class FieldRvaDataReader : IFieldRvaDataReader } var field = table.GetByRid(fieldRvaRow.Field); - int valueSize = DetermineFieldSize(directory, field); + int valueSize = DetermineFieldSize(platform, directory, field); if (fieldRvaRow.Data.CanRead) { @@ -64,7 +64,7 @@ public class FieldRvaDataReader : IFieldRvaDataReader return null; } - private int DetermineFieldSize(IDotNetDirectory directory, in FieldDefinitionRow field) + private int DetermineFieldSize(Platform platform, IDotNetDirectory directory, in FieldDefinitionRow field) { if (!directory.Metadata!.TryGetStream(out var blobStream) || !blobStream.TryGetBlobReaderByIndex(field.Signature, out var reader)) @@ -88,6 +88,9 @@ private int DetermineFieldSize(IDotNetDirectory directory, in FieldDefinitionRow ElementType.U8 => sizeof(ulong), ElementType.R4 => sizeof(float), ElementType.R8 => sizeof(double), + ElementType.I or ElementType.U => directory.Flags.IsLoadedAs32Bit(platform) + ? sizeof(uint) + : sizeof(ulong), ElementType.ValueType => GetCustomTypeSize(directory.Metadata, ref reader), ElementType.Class => GetCustomTypeSize(directory.Metadata, ref reader), _ => throw new ArgumentOutOfRangeException() diff --git a/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs index d1b61cbff..3e0c4b177 100644 --- a/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs @@ -151,6 +151,24 @@ public void ReadVirtualFieldRva() Assert.Equal(new byte[4], data.WriteIntoArray()); } + [Fact] + public void ReadNativeIntFieldRvaX86() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_IntPtrFieldRva_X86); + var data = module.GetModuleType()!.Fields.First(f => f.Name == "__dummy__").FieldRva; + var readableData = Assert.IsAssignableFrom(data); + Assert.Equal(new byte[] {0xEF, 0xCD, 0xAB, 0x89}, readableData.ToArray()); + } + + [Fact] + public void ReadNativeIntFieldRvaX64() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_IntPtrFieldRva_X64); + var data = module.GetModuleType()!.Fields.First(f => f.Name == "__dummy__").FieldRva; + var readableData = Assert.IsAssignableFrom(data); + Assert.Equal(new byte[] {0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01}, readableData.ToArray()); + } + [Theory] [InlineData(nameof(ExplicitOffsetsStruct.IntField), 0)] [InlineData(nameof(ExplicitOffsetsStruct.ByteField), 10)] diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index f3b7009ae..e67584486 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -234,6 +234,20 @@ public static byte[] HelloWorld_VirtualSegment { } } + public static byte[] HelloWorld_IntPtrFieldRva_X86 { + get { + object obj = ResourceManager.GetObject("HelloWorld_IntPtrFieldRva_X86", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_IntPtrFieldRva_X64 { + get { + object obj = ResourceManager.GetObject("HelloWorld_IntPtrFieldRva_X64", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] Assembly1_Forwarder { get { object obj = ResourceManager.GetObject("Assembly1_Forwarder", resourceCulture); diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index 44258e4d4..8ebc736a8 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -99,6 +99,12 @@ ..\Resources\HelloWorld.VirtualSegment.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.IntPtrFieldRva.x86.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.IntPtrFieldRva.x64.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\Assembly1_Forwarder.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.IntPtrFieldRva.x64.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.IntPtrFieldRva.x64.exe new file mode 100644 index 0000000000000000000000000000000000000000..934bc21415f012a85bd44fea107f21066e8e6b08 GIT binary patch literal 4096 zcmeHJU2Ggz6+U-kr^yC8NgIj{q@AqO))+G0#HLDvovh=vYeQ@=-i-}qTW5CmdOgYf zEOTdFFG5j3YDFX}io`>uK9m=ps9GLSsercPl`2w^`T!D$K7f#*B1pWUs^vR(W`7*p zP+kxZ+|m8+J?EZ#{_fu_JoYT@BcdME4?ZAz6)l&X#(xbrFx-FtPy6Y|2XCEtRb0At zqO{>CqaFse(DDr1^8Fw(D$)oWzTx;reyM19K~cR|B_30G-=J5A^)w1?C z%Fv-ymcFx}=!xDI7p=kl97Ehs#eOr%{_AoD2tJp6lzWQkNK7QnPP)yB&nq%3q(~q$3UUGpiV&>3wq5({PkNd?abo`oPiDOvoPof>V zT%;`eytG4)UY^gR&4K;{(-+;Kf|d(Zh}Dmhagd3Bh%vf1jmWs=Vbp~d_(M~VPbrT-TV6e>5fgixWpvN`!UHX>j1?6b!>46u-A&wB7zlx)jrB4u} zK?jLP27wRI3Elq`aG4$fS~R6`UgH-vep#cX(a~tq5RH&YpQbV37<~!&Ia&u!(@VfF z(Cfe=y$SpZ{TgU%{x#qnyc5)OqZJ_~DsUyOhE60eIliR4tTbvhtK!P32>Ybch$KxZCB2H_jw@GWsGPvxIXLgH2cc(07_{6yrQqPYQ)`4;uA^i+@am4M znak3(wscQ*##Dzx(s7@Whoaq$J%yQ{zu{VH}Y(b&n?GQT|d(9$a-uTQ-4%%7g% z2aAt@INra{Ad%WXaNs9jE3XWWzjLz(+%!Xfk0Hc?1A_P-CAyE#PbqcpYG~CL1Ak`A zmURxQv=IhRD$xt!ql1vtY4a`P=3mbK_M5vq{(tU&DFZ3}mKsDi24Jy(cDrFdMMP(J z;j`QghHWL5-`Fw4T8N%S{eF($&}Yd?H$tc0;J%U2>Ene9w8%-~m#TzO3tWY9 ziz3JcpyKkQo^kjt;8O%C2mIPDnvQ6hEMDdK8>b3-L=V#;%$?X==#D$dNmvlr*J z06hU-kOp!Sb$PpYUbFaj6WI0L^X|+}CpUZ!IFD1vokDCLR@FsR26oi;jCSU5UWso1 z?2U;FTb^s+dgb4Y$qa7&j3IqHsNw>i%v>(bo;sT`RAl*8%ME-vnQ2Ouxp2NO-It!Q zk{=rbDttAWX@vecWp7B&Ql~t}4g(deN2lz-J7+1++&rBzJj-|1af7!WFl`UY45O`V zzAEu65jD5@#LrU3@bRimW)_-L^}6fW`lrRT>h;WMtRV^;D&qg559!aDjGhPEO4<$l z!Zi~boKSwX0Y7E65IURq535P_A+?URv>GTKJu_Rd!4LfFxcF!BZtPN)I(P4m*EhL8 z+k5Z2UQ|T^sfb8aDiRM!eQ00$R8)9CrGm5-ZwM5rNPPf_pGZ(4Kq3#kAw>Aj+`B)< z4lNHzedw&`%=w!$=bV{)X1wtDbJR^lJ?MAu61|3zTUNvW4z5Go|KN}N>4$r79eqvA z-#S{{utQl3{Aysj(lR~Ik7QZNpzcZAlewjWbp47l_U}&}OHAiyh~`B%{rbRne%V~@ zHl=Bw$k3nn5MAkwxqtW+Mv0z9XCN_j#eM_R{_{xJ(ZT1|P1$FN4(h-5n2@mwy?C7H zQO2`GPb6z4qIkGW^kHCsfnIT&;9Uqw)YqZX2l`?MZA5A-0?je%SXepnyQwCLE*n7@ zSYT-1vOrirh2GJ!@ZJcN<3o{srOW7S3!!(mtd6piL-ZEn;>NypQ7=6$iH;!;-JoEC zSNosZom(&8sLl+DQ`ZDG<;pN%YQzXI)P`X%HS$2$FwR?Q1XeE;9=#w4A~g1UbKJ-n zW0|p&4>PBi9Ki2EJamm{0F(d8cp(aGuNtz(VI0~|VjQ|upbT&hcj%Ezb2*Gz(0^q5 zg5#Gla)Sx6`caa5nfQkoqx-=Cx<8g_9KN-9oMx6cJ5gRR&T09(_?p;5!^H45y$N_q zFgy#7KSsZY+8$E02F{>(7VxLU@S29tYe<^E2%Ig(b~zgYaWupc#Lqi$pGeUGdQ7s; zp@EmMz96NkZ__tLFQ_Lq^^JiS#6FG?oWF@fl%Y=$LrHsyC4+#+>8Qp(4Y*7X1DZ6Y z;hctF)bL9hni|>~8Z<;BWYA}53~-Dd1N=O#15VQ`fM1|D01NaBz%SFU04>eG3Ydp? zf_knuBc!Aq!H6wtwU5x@7ETijex>fHbF{Wrsk?4tZH?T}@&m^%(?#Vt{wnYaP5WNx zJBn5VJ5uwurzoe&^=j2DJ8CMzIVsm8MN?s@+_KXs+EFLFXa-dk&7x4Lr~KgRPW@Ti zQ7bA4ZQt7|ob%TGz%?TXP3N9kaB$tO)&s5AHZtwIHQUk5W#yP#8VlRB9L9X111}&Jj*V0z& zjTUGmhnhM=zh3|D!1*J%ZQK<-U!S>9+R$(dUM~f;dwG&kG%U4QCeoCH- z#xos4qgE*s6)sMl`rK(smVdChJc9hn3n#wy%H~`0#$V6;=IeKllg#eo`Uk8X8R*h? zsKhI30J41in(ENvuZQT&E__zH&R|=M^*45i*b32e=r3l8-sa<&v}hV{tyru%Z+DGj zT*2+K1~@~@z~*R)7D3Me&I0H5de5DI#dl0|ay*{b()g}vN)i_3fCyMMQDbJwMx9Zx z>EUMcwaPK#8>$GY32q3vNfGpXP;vV~&p3P+@F{|p4SsbOO9!=12Dfqi8>ccbqSLew zbEjd?rJ7!&frv~{s0e87O4zQ0%s;2+H9gKjJH(C4>({pVcdz*w6hqPSa|QDQSZK#} z5;u6J)p@-G(C5rVTGxY*PKT$vtz$rQ3!U3A%Bi4vP*pvXBd$?Hdt9XrSS6C^vwEGS zgwNUJb((Q96=&w%){FC+haDd`M;*C|I=sDiUNd;J37q=QdHMZ@G>{DdgwHWE#~zEtO~a6%@4pl^=g;j=?N3dZ$%971gcnYx>w$MRv^JD$h9_Lx3jEMtk4{*=d)5qHWAkKM zx~6BZquMqvF1;R%N!cv>o^R8}2Nw?;yP6R0Qa@KaUtfxU_MS5<}Y(dt;!szhts!9{4*`GTKU zo63<6!^yN6&Uu^uRTZRV-JY`WC_x(6O(#@IP}<1oF5Wa Date: Sat, 29 Apr 2023 19:48:21 +0200 Subject: [PATCH 11/26] BUGFIX: GetHashCode on TypeDefOrRefSignature should return same hashcode as basic ITypeDescriptors. --- .../SignatureComparer.ResolutionScope.cs | 5 +--- .../SignatureComparer.TypeDefOrRef.cs | 23 ++++++++++++++----- .../SignatureComparer.TypeSignature.cs | 20 ++++++---------- .../Signatures/SignatureComparer.cs | 2 +- .../Signatures/SignatureComparerTest.cs | 14 +++++++++++ 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs index b2306bdfb..7fb93acbb 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs @@ -90,14 +90,11 @@ public int GetHashCode(AssemblyDescriptor obj) unchecked { int hashCode = obj.Name is null ? 0 : obj.Name.GetHashCode(); - hashCode = (hashCode * 397) ^ (obj.Culture is not null ? obj.Culture.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (int) TableIndex.AssemblyRef; + hashCode = (hashCode * 397) ^ (!Utf8String.IsNullOrEmpty(obj.Culture) ? obj.Culture.GetHashCode() : 0); if (!AcceptNewerAssemblyVersionNumbers && !AcceptOlderAssemblyVersionNumbers) hashCode = (hashCode * 397) ^ obj.Version.GetHashCode(); - hashCode = (hashCode * 397) ^ (int) obj.Attributes; - byte[]? publicKeyToken = obj.GetPublicKeyToken(); hashCode = (hashCode * 397) ^ (publicKeyToken is not null ? GetHashCode(publicKeyToken) : 0); diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs index 83de4c287..9375b8e85 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.PE.DotNet.Metadata.Tables; namespace AsmResolver.DotNet.Signatures { @@ -30,12 +31,20 @@ public bool Equals(ITypeDescriptor? x, ITypeDescriptor? y) } /// - public int GetHashCode(ITypeDescriptor obj) + public int GetHashCode(ITypeDescriptor obj) => obj switch + { + InvalidTypeDefOrRef invalidType => GetHashCode(invalidType), + ITypeDefOrRef typeDefOrRef => GetHashCode(typeDefOrRef), + TypeSignature signature => GetHashCode(signature), + _ => SimpleTypeHashCode(obj) + }; + + private int SimpleTypeHashCode(ITypeDescriptor obj) { unchecked { - int hashCode = obj.Name is null ? 0 : obj.Name.GetHashCode(); - hashCode = (hashCode * 397) ^ (obj.Namespace is null ? 0 : obj.Namespace.GetHashCode()); + int hashCode = obj.Name?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (obj.Namespace?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (obj.DeclaringType is null ? 0 : GetHashCode(obj.DeclaringType)); return hashCode; } @@ -67,19 +76,21 @@ private bool SimpleTypeEquals(ITypeDescriptor x, ITypeDescriptor y) public bool Equals(ITypeDefOrRef? x, ITypeDefOrRef? y) => Equals(x as ITypeDescriptor, y); /// - public int GetHashCode(ITypeDefOrRef obj) => GetHashCode((ITypeDescriptor) obj); + public int GetHashCode(ITypeDefOrRef obj) => obj.MetadataToken.Table == TableIndex.TypeSpec + ? GetHashCode((TypeSpecification) obj) + : SimpleTypeHashCode(obj); /// public bool Equals(TypeDefinition? x, TypeDefinition? y) => Equals(x as ITypeDescriptor, y); /// - public int GetHashCode(TypeDefinition obj) => GetHashCode((ITypeDescriptor) obj); + public int GetHashCode(TypeDefinition obj) => SimpleTypeHashCode(obj); /// public bool Equals(TypeReference? x, TypeReference? y) => Equals(x as ITypeDescriptor, y); /// - public int GetHashCode(TypeReference obj) => GetHashCode((ITypeDescriptor) obj); + public int GetHashCode(TypeReference obj) => SimpleTypeHashCode(obj); /// public bool Equals(TypeSpecification? x, TypeSpecification? y) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs index e31005637..fa11e5a67 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs @@ -187,17 +187,7 @@ public bool Equals(TypeDefOrRefSignature? x, TypeDefOrRefSignature? y) } /// - public int GetHashCode(TypeDefOrRefSignature obj) - { - unchecked - { - int hashCode = (int) obj.ElementType << ElementTypeOffset; - hashCode = (hashCode * 397) ^ obj.Name.GetHashCode(); - hashCode = (hashCode * 397) ^ (obj.Namespace is null ? 0 : obj.Namespace.GetHashCode()); - hashCode = (hashCode * 397) ^ (obj.Scope is null ? 0 : GetHashCode(obj.Scope)); - return hashCode; - } - } + public int GetHashCode(TypeDefOrRefSignature obj) => SimpleTypeHashCode(obj); /// public bool Equals(CustomModifierTypeSignature? x, CustomModifierTypeSignature? y) @@ -274,8 +264,12 @@ private bool Equals(TypeSpecificationSignature? x, TypeSpecificationSignature? y return Equals(x.BaseType, y.BaseType); } - private int GetHashCode(TypeSpecificationSignature obj) => - (int) obj.ElementType << ElementTypeOffset ^ GetHashCode(obj.BaseType); + private int GetHashCode(TypeSpecificationSignature obj) => SimpleTypeSpecHashCode(obj); + + private int SimpleTypeSpecHashCode(TypeSpecificationSignature obj) + { + return (int) obj.ElementType << ElementTypeOffset ^ GetHashCode(obj.BaseType); + } /// public bool Equals(ArrayTypeSignature? x, ArrayTypeSignature? y) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs index c4a67b84d..081b92f20 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs @@ -8,7 +8,7 @@ namespace AsmResolver.DotNet.Signatures public partial class SignatureComparer : IEqualityComparer { - private const int ElementTypeOffset = 24; + private const int ElementTypeOffset = 8; private const SignatureComparisonFlags DefaultFlags = SignatureComparisonFlags.ExactVersion; /// diff --git a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs index aed57cede..0cab43cfa 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs @@ -205,6 +205,20 @@ public void CorlibComparison() Assert.False(set.Add(reference2)); } + [Fact] + public void CompareSimpleTypeDescriptors() + { + var assembly = new DotNetFrameworkAssemblyResolver().Resolve(KnownCorLibs.MsCorLib_v4_0_0_0); + var definition = assembly.ManifestModule!.TopLevelTypes.First(x => x.IsTypeOf("System.IO", "Stream")); + var reference = definition.ToTypeReference(); + var signature = reference.ToTypeSignature(); + + Assert.Equal((ITypeDescriptor) reference, signature, _comparer); + Assert.Equal((ITypeDescriptor) definition, signature, _comparer); + Assert.Equal(_comparer.GetHashCode(reference), _comparer.GetHashCode(signature)); + Assert.Equal(_comparer.GetHashCode(definition), _comparer.GetHashCode(signature)); + } + private class NestedTypes { public class FirstType From 59c1e35d227278be7a0f2156dd2dc9a47a0d443a Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 29 Apr 2023 19:55:02 +0200 Subject: [PATCH 12/26] Update docs. --- docs/dotnet/advanced-module-reading.rst | 27 +++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/dotnet/advanced-module-reading.rst b/docs/dotnet/advanced-module-reading.rst index fa0907281..e84ff94aa 100644 --- a/docs/dotnet/advanced-module-reading.rst +++ b/docs/dotnet/advanced-module-reading.rst @@ -19,11 +19,11 @@ These parameters can then be passed on to any of the ``ModuleDefinition.FromXXX` PE image reading parameters --------------------------- -.NET modules are stored in a normal PE file. To customize the way AsmResolver reads the underlying PE image before it is being interpreted as a .NET image, ``ModuleReaderParameters`` provides a ``PEReaderParameters`` property that can be modified or replaced completely. +.NET modules are stored in a normal PE file. To customize the way AsmResolver reads the underlying PE image before it is being interpreted as a .NET image, ``ModuleReaderParameters`` provides a ``PEReaderParameters`` property that can be modified or replaced completely. .. code-block:: csharp - parameters.PEReaderParameters = new PEReaderParameters + parameters.PEReaderParameters = new PEReaderParameters { ... }; @@ -34,7 +34,7 @@ For example, this can be in particular useful if you want to let AsmResolver ign parameters.PEReaderParameters.ErrorListener = EmptyErrorListener.Instance; - + Alternatively, this property can also be set through the constructor of the ``ModuleReaderParameters`` class directly: .. code-block:: csharp @@ -53,7 +53,7 @@ Modules often depend on other assemblies. These assemblies often are placed in t parameters.WorkingDirectory = @"C:\Path\To\Different\Folder"; - + Alternatively, this property can also be set through the constructor of the ``ModuleReaderParameters`` class directly: .. code-block:: csharp @@ -83,10 +83,10 @@ To let the reader use this implementation of the ``INetModuleResolver``, set the parameters.NetModuleResolver = new CustomNetModuleResolver(); -Custom method body readers +Custom method body readers -------------------------- -Some .NET obfuscators store the implementation of method definitions in an encrypted form, use native method bodies, or use a custom format that is interpreted at runtime by the means of JIT hooking. To change the way of how method bodies are being read, it is possible to provide a custom implementation of the ``IMethodBodyReader`` interface, or extend the default implementation. +Some .NET obfuscators store the implementation of method definitions in an encrypted form, use native method bodies, or use a custom format that is interpreted at runtime by the means of JIT hooking. To change the way of how method bodies are being read, it is possible to provide a custom implementation of the ``IMethodBodyReader`` interface, or extend the default implementation. Below an example of how to add support for reading simple x86 method bodies: @@ -95,11 +95,11 @@ Below an example of how to add support for reading simple x86 method bodies: public class CustomMethodBodyReader : DefaultMethodBodyReader { public override MethodBody ReadMethodBody( - ModuleReaderContext context, - MethodDefinition owner, + ModuleReaderContext context, + MethodDefinition owner, in MethodDefinitionRow row) { - if (owner.IsNative && row.Body.CanRead) + if (owner.IsNative && row.Body.CanRead) { // Create raw binary reader if method is native. var reader = row.Body.CreateReader(); @@ -109,7 +109,7 @@ Below an example of how to add support for reading simple x86 method bodies: // a very accurate heuristic for finding the boundaries of native // method bodies. - var code = reader.ReadBytesUntil(0xC3); + var code = reader.ReadBytesUntil(0xC3); // Create native method body. return new NativeMethodBody(owner, code); @@ -128,7 +128,7 @@ To let the reader use this implementation of the ``IMethodBodyReader``, set the parameters.MethodBodyReader = new CustomMethodBodyReader(); -Custom Field RVA reading +Custom Field RVA reading ------------------------ By default, the field RVA data storing the initial binary value of a field is interpreted as raw byte blobs, and are turned into instances of the ``DataSegment`` class. To adjust this behaviour, it is possible to provide a custom implementation of the ``IFieldRvaDataReader`` interface. @@ -139,8 +139,9 @@ By default, the field RVA data storing the initial binary value of a field is in public class CustomFieldRvaDataReader : FieldRvaDataReader { public override ISegment ResolveFieldData( - IErrorListener listener, - IMetadata metadata, + IErrorListener listener, + Platform platform, + IDotNetDirectory directory, in FieldRvaRow fieldRvaRow) { // ... From 2c99d0ab215aadeab7d45f2e29eeb32bd30120e2 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 3 May 2023 11:45:55 +0200 Subject: [PATCH 13/26] Extend PE file documentation. --- docs/pefile/basics.rst | 15 ++- docs/pefile/headers.rst | 216 ++++++++++++++++++++++++++++++++++++++- docs/pefile/index.rst | 9 +- docs/pefile/sections.rst | 104 ++++++++++++++++--- 4 files changed, 317 insertions(+), 27 deletions(-) diff --git a/docs/pefile/basics.rst b/docs/pefile/basics.rst index d893d7a0f..a5636ed60 100644 --- a/docs/pefile/basics.rst +++ b/docs/pefile/basics.rst @@ -8,10 +8,23 @@ Every raw PE file interaction is done through classes defined by the ``AsmResolv using AsmResolver.PE.File; +Creating a new PE file +---------------------- + +Creating a PE file can be done through one of the ``PEFile`` constructors: + +.. code-block:: csharp + + var peFile = new PEFile(); + + +This will create a new empty PE file with 0 sections, and sets some values in the file header and optional header that are typical for a 32-bit Windows console application targeting the x86 platform. + + Opening a PE file ----------------- -Opening a file can be done through one of the `FromXXX` methods: +Opening a PE file can be done through one of the ``FromXXX`` methods: .. code-block:: csharp diff --git a/docs/pefile/headers.rst b/docs/pefile/headers.rst index f320af19e..2b6152271 100644 --- a/docs/pefile/headers.rst +++ b/docs/pefile/headers.rst @@ -1,12 +1,218 @@ PE Headers ========== -After you obtained an instance of the ``PEFile`` class, it is possible to read and edit various properties in the DOS header, COFF file header and optional header. They each have a designated property: +After obtaining an instance of the ``PEFile`` class, it is possible to read and edit various properties in the DOS header, COFF file header and optional header. + +All relevant code for this article is found in the following namespace: + +.. code-block:: csharp + + using AsmResolver.PE.File.Headers; + + + +DOS Header +---------- + +The DOS header (also known as the MZ header or ``IMAGE_DOS_HEADER``) is the first header in every PE file, and is represented using the ``DosHeader`` class in AsmResolver. +While the minimal DOS header is 64 bytes long, and often is followed by a stub of MS DOS code, only one field is read and used by Windows while preparing the PE file for execution. +This field (``e_lfanew``) is the offset to the NT Headers (``IMAGE_NT_HEADERS``), which contains the COFF and Optional Header. + +Typically this value is set to ``0x80``, but AsmResolver supports reading and changing this offset if desired: + +.. code-block:: csharp + + PEFile file = ... + + // Obtain e_lfanew: + Console.WriteLine("e_flanew: {0:X8}", file.DosHeader.NextHeaderOffset); + + // Set a new e_lfanew: + file.DosHeader.NextHeaderOffset = 0x100; + + + +File Header +----------- + +The file header describes general characteristics of the PE file. +In particular, it indicates the target architecture, as well as the total size of the optional header and number of sections stored in the PE file. + +AsmResolver exposes the file header via the ``PEFile::FileHeader`` property. +The properties defined in this object correspond directly with the fields in ``IMAGE_FILE_HEADER`` as defined in ``winnt.h``, and are both readable and writeable: + +.. code-block:: csharp + + PEFile file = ... + FileHeader header = file.FileHeader; + + Console.WriteLine($"Machine: {header.Machine}"); + Console.WriteLine("NumberOfSections: {header.NumberOfSections}"); + Console.WriteLine("TimeDateStamp: 0x{header.TimeDateStamp:X8}"); + Console.WriteLine("PointerToSymbolTable: 0x{header.PointerToSymbolTable:X8}"); + Console.WriteLine("NumberOfSymbols: {header.NumberOfSymbols}"); + Console.WriteLine("SizeOfOptionalHeader: 0x{header.SizeOfOptionalHeader:X4}"); + Console.WriteLine("Characteristics: {header.Characteristics}"); + + +.. note:: + + While ``NumberOfSections`` and ``SizeOfOptionalHeader`` are writeable, these properties are automatically updated when using ``PEFile::Write`` to ensure a valid PE file to be written to the disk. + + + +Optional Header +--------------- + +The optional header directly follows the file header of a PE file, and describes information such as the entry point, as well as file alignment and target subsystem. +It also contains the locations of important data directories stored in the PE file containing information such as import address tables and resources. + +AsmResolver exposes the file header via the ``PEFile::OptionalHeader`` property. + +.. code-block:: csharp + + PEFile file = ... + OptionalHeader header = file.OptionalHeader; + + +PE32 and PE32+ Format +~~~~~~~~~~~~~~~~~~~~~ + +While the PE specification defines both a 32-bit and 64-bit version of the structure, AsmResolver abstracts away the differences using a single ``OptionalHeader`` class. +The final file format that is used is dictated by the ``Magic`` property. +Changing the file format can be done by simply writing to this property: + +.. code-block:: csharp + + // Read currently used file format. + Console.WriteLine($"Magic: {header.Magic}"); + + // Change to PE32+ (64-bit format). + header.Magic = OptionalHeaderMagic.PE32Plus; + + +.. warning:: + + For a valid PE file, it is important to use the right file format of the optional header that matches with the target architecture as specified in ``FileHeader::Machine``. + A 32-bit target architecture will always expect a ``PE32`` file format of the optional header, while a 64-bit architecture will require a ``PE32Plus`` format. + AsmResolver does not automatically keep these two properties in sync. + + +Entry Point and Data Directories +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The optional header references many segments in the sections of the PE file via the ``AddressOfEntryPoint`` and ``DataDirectories`` properties. + + +.. code-block:: csharp + + // Reading the entry point: + Console.WriteLine($"AddressOfEntryPoint: 0x{header.AddressOfEntryPoint:X8}"); + + // Setting a new entry point: + header.AddressOfEntryPoint = 0x12345678; + + +Iterating all data directory headers can be done using the following: + +.. code-block:: csharp + + for (int i = 0; i < header.DataDirectories.Count; i++) + { + var directory = header.DataDirectories[i]; + Console.WriteLine($"[{i}]: RVA = 0x{directory.VirtualAddress:X8}, Size = 0x{directory.Size:X8}"); + } + + +Getting or setting a specific data directory header can also be done by using its symbolic index via ``GetDataDirectory`` and ``SetDataDirectory``: + +.. code-block:: csharp + + // Get the import directory. + var directory = header.GetDataDirectory(DataDirectoryIndex.ImportDirectory); + + // Set the import directory. + header.SetDataDirectory(DataDirectoryIndex.ImportDirectory, new DataDirectory( + virtualAddress: 0x00002000, + size: 0x80 + )); + + +Reading the contents behind these pointers can be done e.g., by using ``PEFile::CreateReaderAtRva`` or ``PEFile::CreateDataDirectoryReader``: + +.. code-block:: csharp + + BinaryStreamReader entryPointReader = file.CreateReaderAtRva(header.AddressOfEntryPoint); + .. code-block:: csharp - 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); + BinaryStreamReader importsReader = file.CreateDataDirectoryReader( + header.GetDataDirectory(DataDirectoryIndex.ImportDirectory) + ); + + +These functions throw when an invalid offset or size are provided. +It is also possible to use the ``TryCreateXXX`` methods instead, to immediately test for validity and only return the reader if correct information was provided: + +.. code-block:: csharp + + var importDirectory = header.GetDataDirectory(DataDirectoryIndex.ImportDirectory); + if (file.TryCreateDataDirectoryReader(importDirectory, out var importsReader)) + { + // Valid RVA and size. Use importReader to read the contents. + } + + +Sub System +~~~~~~~~~~ + +The ``SubSystem`` field in the optional header defines the type of sub system the executable will be run in. +Typical values are either ``WindowsGui`` for graphical applications, and ``WindowsCui`` for applications requiring a console window. + +.. code-block:: csharp + + // Reading the target sub system: + Console.WriteLine("SubSystem: {header.SubSystem}"); + + // Changing the application to a GUI application: + header.SubSystem = SubSystem.WindowsGui; + + +Section Alignments +~~~~~~~~~~~~~~~~~~ + +The optional header defines two properties ``FileAlignment`` and ``SectionAlignment`` that determine the section alignment stored on the disk and in memory at runtime respectively. + +.. code-block:: csharp + + Console.WriteLine("FileAlignment: 0x{header.FileAlignment}"); + Console.WriteLine("SectionAlignment: 0x{header.SectionAlignment}"); + + +AsmResolver respects the value in ``FileAlignment`` when writing a ``PEFile`` object to the disk, and automatically realigns sections when appropriate. +It is also possible to force the realignment of sections to be done immediately after assigning a new value to these properties using the ``PEFile::AlignSections`` method. + +.. code-block:: csharp + + header.FileAlignment = 0x400; + file.AlignSections(); + + +See :ref:`pe-file-sections` for more information on how to use sections. + + +Other PE Offsets and Sizes +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The optional header defines a few more properties indicating some important offsets and sizes in the PE file: + +- ``SizeOfCode`` +- ``SizeOfInitializedData`` +- ``SizeOfUninitializedData`` +- ``BaseOfCode`` +- ``BaseOfData`` +- ``SizeOfImage`` +- ``SizeOfHeaders`` -Every change made to these headers will be reflected in the output executable, however very little verification on these values is done. +These properties can be read and written in the same way other fields are read and written, but are automatically updated when using ``PEFile::Write`` to ensure a valid binary. \ No newline at end of file diff --git a/docs/pefile/index.rst b/docs/pefile/index.rst index 3422d68a7..913480266 100644 --- a/docs/pefile/index.rst +++ b/docs/pefile/index.rst @@ -1,8 +1,9 @@ Overview ======== -The PE file layer is the lowest level of abstraction of the portable executable (PE) file format. It's main purpose is to read and write raw executable files from and to the disk. It is mainly represented by the ``PEFile`` class, and provides access to the raw top-level PE headers, including the DOS header, COFF file header and optional header. It also exposes for each section the section header and raw contents, which can be read by the means of an ``IBinaryStreamReader`` instance. +The PE file layer is the lowest level of abstraction of the portable executable (PE) file format. It's main purpose is to read and write raw executable files from and to the disk. +It is mainly represented by the ``PEFile`` class, and provides access to the raw top-level PE headers, including the DOS header, COFF file header and optional header. +It also exposes for each section the section header and raw contents, which can be read by the means of an ``BinaryStreamReader`` instance. -It is important to note that this layer mainly leaves the interpretation of the data to the user. You will not find any methods or properties returning models of what is stored in these sections. Interpretations of e.g. the import directory can be found one layer up, using the ``PEImage`` class. - -Because of this primitive nature, many will probably not find this layer all that useful. If, however, you need to change anything in the PE headers that other layers of abstraction do not provide (like adding extra PE sections), this layer of abstraction can be very useful as the final step in the pipeline. \ No newline at end of file +It is important to note that this layer mainly leaves the interpretation of the data to the user. You will not find any methods or properties returning models of what is stored in these sections. +Interpretations of e.g. the import directory can be found one layer up, using the ``PEImage`` class. \ No newline at end of file diff --git a/docs/pefile/sections.rst b/docs/pefile/sections.rst index 2ff88951c..5da9a7edc 100644 --- a/docs/pefile/sections.rst +++ b/docs/pefile/sections.rst @@ -7,43 +7,113 @@ Sections can be read and modified by accessing the ``PEFile.Sections`` property, .. code-block:: csharp - foreach (var section in peFile.Sections) - { - Console.WriteLine(section.Name); - } + PEFile file = ... -Each ``PESection`` object also has the ``Contents`` property defined, which is a `IReadableSegment`. This object is capable of creating a `IBinaryStreamReader` instance: + foreach (var section in file.Sections) + { + Console.WriteLine(section.Name); + Console.WriteLine($"\tFile Offset: 0x{section.Offset:X8}"); + Console.WriteLine($"\tRva: 0x{section.Rva:X8}"); + Console.WriteLine($"\tFile Size: 0x{section.Contents.GetPhysicalSize():X8}"); + Console.WriteLine($"\tVirtual Size: 0x{section.Contents.GetVirtualSize():X8}"); + } + + +Reading Section Data +~~~~~~~~~~~~~~~~~~~~ + +Each ``PESection`` object has a ``Contents`` property defined of type ``IReadableSegment``. +This object is capable of creating a ``BinaryStreamReader`` instance to read and parse data from the section: + +.. code-block:: csharp + + var reader = section.CreateReader(); + + +If you want to get the entire section in a byte array, you can take the ``ToArray`` shortcut: .. code-block:: csharp - var reader = section.CreateReader(); + byte[] data = section.ToArray(); -This can be used to read the data that is present in the section. If you want to get the entire section in a byte array, you can take the ``ToArray`` shortcut: + +Alternatively, if you have a file offset or RVA to start read from, it is also possible use one of the ``PEFile::CreateReaderAtXXX`` methods: + +.. code-block:: csharp + + var reader = file.CreateReaderAtOffset(0x200); + .. code-block:: csharp - byte[] data = section.ToArray(); + var reader = file.CreateReaderAtRva(0x2000); -The ``Sections`` property is mutable, which means you can add new sections and remove others from the PE. +These methods will automatically find the right section to read from, and provide a reader that points to the start of this data. + + +Adding a new Section +~~~~~~~~~~~~~~~~~~~~ + +The ``Sections`` property is mutable, which means it is possible to add new sections and remove others from the PE. +New sections can be created using the ``PESection`` constructors: .. code-block:: csharp - var section = new PESection(".asmres", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); - section.Contents = new DataSegment(new byte[] {1, 2, 3, 4}); + var section = new PESection(".asmres", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); + section.Contents = new DataSegment(new byte[] {1, 2, 3, 4}); - peFile.Sections.Add(section); + file.Sections.Add(section); -Some sections (such as `.data` or `.bss`) contain uninitialized data, and might be resized in virtual memory at runtime. As such, the virtual size of the contents might be different than its physical size. You can use `VirtualSegment` to decorate a normal `ISegment` with a different virtual size. +Some sections (such as ``.data`` or ``.bss``) contain uninitialized data, and might be resized in virtual memory at runtime. +As such, the virtual size of the contents might be different than its physical size. +To make dynamically sized sections, it is possible to use the ``VirtualSegment`` to decorate a normal `ISegment` with a different virtual size. .. code-block:: csharp - var section = new PESection(".asmres", SectionFlags.MemoryRead | SectionFlags.ContentUninitializedData); - var physicalContents = new DataSegment(new byte[] {1, 2, 3, 4}); - section.Contents = new VirtualSegment(physicalContents, 0x1000); // Create a new segment with a virtual size of 0x1000 bytes. + var section = new PESection(".asmres", SectionFlags.MemoryRead | SectionFlags.ContentUninitializedData); + var physicalContents = new DataSegment(new byte[] {1, 2, 3, 4}); + section.Contents = new VirtualSegment(physicalContents, 0x1000); // Create a new segment with a virtual size of 0x1000 bytes. - peFile.Sections.Add(section); + file.Sections.Add(section); For more advanced section building, see :ref:`pe-building-sections` and :ref:`segments`. + + +Updating Section Offsets +~~~~~~~~~~~~~~~~~~~~~~~~ + +For performance reasons, offsets and sizes are not computed unless you explicitly tell AsmResolver to align all sections and update all offsets within a section. +To force a recomputation of all section offsets and sizes, you can use the ``PEFile::AlignSections`` method: + +.. code-block:: csharp + + PESection section = ...; + file.Sections.Add(section); + + file.AlignSections(); + + Console.WriteLine("New section RVA: 0x{section.Rva:X8}"); + + +If you want to align the sections and also automatically update the fields in the file and optional header of the PE file, it is also possible to use ``PEFile::UpdateHeaders`` instead: + +.. code-block:: csharp + + PESection section = ...; + file.Sections.Add(section); + + file.UpdateHeaders(); + + Console.WriteLine("New section RVA: 0x{section.Rva:X8}"); + Console.WriteLine("New section count: {file.FileHeader.NumberOfSections}"); + + +.. warning:: + + While both ``AlignSections`` and ``UpdateHeaders`` do a traversal of the segment tree, they may not update all offsets and sizes stored in the sections themselves. + When reading a PE file using any of the ``PEFile::FromXXX``, AsmResolver initializes every section's ``Contents`` property with a single contiguous chunk of raw memory, and does not parse any of the section contents. + As such, if some code or data stored in one of these raw section references code or data in another, this will not be automatically updated. + If, however, the ``Contents`` property is an ``ISegment`` that does implement ``UpdateOffsets`` appropriately (e.g., when using a ``SegmentBuilder``), then all references stored in such a segment will be updated accordingly. \ No newline at end of file From 78753a0bf669a4a87c3e1287f33e58f311be2146 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 5 May 2023 20:00:37 +0200 Subject: [PATCH 14/26] Add OriginalFirstThunk == 0 test. --- .../Imports/ModuleImportTest.cs | 26 +++++++++++++++--- .../Properties/Resources.Designer.cs | 7 +++++ .../Properties/Resources.resx | 3 ++ .../Resources/HelloWorld.upx.exe | Bin 0 -> 7168 bytes 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.upx.exe diff --git a/test/AsmResolver.PE.Tests/Imports/ModuleImportTest.cs b/test/AsmResolver.PE.Tests/Imports/ModuleImportTest.cs index 3f91a87f9..1dd79d15e 100644 --- a/test/AsmResolver.PE.Tests/Imports/ModuleImportTest.cs +++ b/test/AsmResolver.PE.Tests/Imports/ModuleImportTest.cs @@ -9,7 +9,7 @@ public class ModuleImportTest public void ReadDotNetHelloWorld() { var peImage = PEImage.FromBytes(Properties.Resources.HelloWorld); - + Assert.Single(peImage.Imports); Assert.Equal("mscoree.dll", peImage.Imports[0].Name); Assert.Single(peImage.Imports[0].Symbols); @@ -20,11 +20,29 @@ public void ReadDotNetHelloWorld() public void ReadSimpleDll() { var peImage = PEImage.FromBytes(Properties.Resources.SimpleDll); - + var module = peImage.Imports.First(m => m.Name == "ucrtbased.dll"); Assert.NotNull(module); Assert.Contains("__stdio_common_vsprintf_s", module.Symbols.Select(m => m.Name)); } - + + [Fact] + public void ReadTableWithEmptyOriginalFirstThunk() + { + // https://github.com/Washi1337/AsmResolver/issues/431 + + var peImage = PEImage.FromBytes(Properties.Resources.HelloWorld_UPX); + + var module = peImage.Imports.First(m => m.Name == "KERNEL32.DLL"); + Assert.NotNull(module); + + Assert.Equal(new[] + { + "LoadLibraryA", + "ExitProcess", + "GetProcAddress", + "VirtualProtect", + }, module.Symbols.Select(x => x.Name)); + } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 86d6f305d..61ac7d57a 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -150,6 +150,13 @@ public static byte[] HelloWorld_DoubleUserStringsStream_EnC { } } + public static byte[] HelloWorld_UPX { + get { + object obj = ResourceManager.GetObject("HelloWorld_UPX", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] HelloWorldPortablePdb { get { object obj = ResourceManager.GetObject("HelloWorldPortablePdb", resourceCulture); diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index 93af0f6bb..5ffcc282d 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -63,6 +63,9 @@ ..\Resources\HelloWorld.DoubleUserStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.upx.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\HelloWorld.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.upx.exe b/test/AsmResolver.PE.Tests/Resources/HelloWorld.upx.exe new file mode 100644 index 0000000000000000000000000000000000000000..0273c943982e8445fe167baf36e12072a918b124 GIT binary patch literal 7168 zcmeHM3sh5A);>vY-T{IE0eKi+29-C6A|OHD6*MYO%PS-|5X^%nHvuY2Fi?#V#TT_c zMy=M?8S8*AP^tuQ1S=}l4ut}OmRcM@Q*{8#BXh%Om%7@SHUIk8`q%o`?6vm!?*7g` z`|NY>P43A~{JKhj0{}p%8Vmr`p=cELO#5#d3;?EcFPnn%YaB3xzk-njJ-Yz#M&lUc_MrRuuF&=ZVD~Qh8}{iE0Cc~>{~nddN%5ZP z%R<{%dA-j2^%xZZ{ivVMjLk&bd{k$l`f7dX)r_!FKTnxl#xq*RSe2+6H)J-dZwUK8 zOYx17|GoSl?0~2?_~P z*7ugsGRky_y!)Xk=~^P`#S;A2SD(%;`tU$%YPn9anqCI=ca5#TZ-P*BH`wn@Wt@k4 zTNelH5pd=VR%YNOPqZ6NkJ<z>iClamZ2r;n>nk(`5@>aBg&mvt+`I`RMkKRt5flHzcK_WIBwZTxnO7HA^-SC@3w zng?#RZ`Q(t%Y0QEe*$$ExH;^eMGO4({07HKr2|OK%y|Ds#pl$zr#tlG$Nj{s2i+gL z^e1YEr)oyF=Sd-Z5{a2hhJfy7eaj9Ci9?{JH?vBIU2u8no0@B^X3@*1^RTJ6<{x*o ze)Q~O7oR>NX+j{uc582fiOadPDuF$~GbdfUm5W!5d%QTb)56iM z6(o(`IWl5LgqRhPBzJ9j&*Y=~9l2W?Pf#((>beZ!EqYuBzCHa;RIZbfg+E>6OyA()HlQ}|W8K4;~T ze$d^ntM6{=j$cO+1mdsgzfC_z2rhkj)$(c`jq^8p+$Ac;VQawD`Z{aHGU+ATg^irm z-MAf+#_?#Dy3hp_I}VA5=2q2TXlES&wxvr{p=+qhU-mX?8<|WU^?{>x)Er=@O2a(h zJ|Gue(rj70S=dz-Ot7QPGE1>33vefvWvau+*N(9bR*G~QVTigS_4JfAF=C`M#stz?hCpzUVd9KPtm7sHT~)vW$lSetL%nM zE!|Dbqj@_w9v`ApTQz}W-(-@YzR~W|j6Y%7&2@ln*4&XUfL-|Zh zqum(!d8CTmROg%3jL&`fy;sGdT3<*_a?#*Tu25GUw!y-IpU`S_ic}uYQ-v?)t(_iv zR?64Llk#w*U$sCkZRWW(2O25}n%PJCy6cO#Y09@x(KQuY@%zdStCTm3mt82nQpnwR zf)KsKF0f+%VWd3&Gts5CgU(;(9@<%csCe6=Rkg``Nfk$13Yw!X8Va@S$vlpIgu?eTk#ry{!-Z^jWQo#ub?(P}u_MA1|IH!ALy1DsA6SV1vab^la>jLSEcNmXA8zYu;(zt(JD z9{qY^!}$;POT+P+b#FjLqX5wHo|4) z6U-W&0dcw$@tyiQWHn&ZhR7c8UwG({{$9cHMl#GdEc&Z9!lgxgPAn4PDi-!fCLjA| z#g|PdJ^lW2kXSUnVoTNqE!9N6l<)jO|CQs5f0aUw`*=2mbq9L}r0|B)zYL8DmKKhxC$)g;rI{p5c_603xY}7j{M#>FM8+)XmYM;Mf zsUd0{G^S(YWH?_BG7GdFzOG2lAhB_y&EHk-8DSzpvzhp?pOhu!ea@XhoeGEO z%l%ey_X&(m&P_p03X`+f7Wv%cdMt_H-%wjvM_oVQTH?9{?;jk63cFa9V#F^_96e;j z{d+6xjF?rneX9{goS5@Q810DWHRZ()Q{JtiUhMW z>67-8>Mu%>&{I(Jnc+_@meuWeateFI^1$|u$bwTO$YNJtwB!%lP4+P9rgb~(DtC$J zWfL^$q3~pvX+-A&ocBujoajKd(rzK?*@8_cg$FE}PVWY5Si@Z>yKuqzAF>!v+=ya= z?LcR{)rY3@?=4C{hgUOj${hoh3KW*i5=BnwD%VAZlfs@2( zhR97v7~r}P>h4l|7oHZ({rWwoH7>VmqC^uz5J}yc1yY%a??~CXAwafxHIq7vjiJ9+ z1f^JEzMU1}zsu*mFDEW)#)aDE=0z_ZEmw6%~1u!-}C?b_{4O$ z?au=jQ&*VNSDBH`um(fUrS*1pg=-_$_I&omrvugM=-RV^4Vmvx8Xg#qPretvG%ZmM z%j9Wcd~rwGFU=%F1T0^VNb}QfC%199y!97u?z60;uYtLN8SL@B)}HzN?7!gFDGfj8 zL_~)t#DODwX`b~h!&Zw65f`z<&5I%Aj~sdQl8bO%7U@Xd-aJ8$^Vy$D4zxnyYZ6>i z!o%7aqiLO17VWHc-!cR-e_+Ihb%uGKu-)Ag9@M|i@eX;z0D}`Y_;O{deBE$D*mwE^ zw+gx#>kQ*D7};pofO0Y0$;?GwID2x%0S~K)G^JF8i22J8^6wqhV?P9&dnc-IAAfsX6e?Pn$QrZ%lS#9$p|1TTMXA5`g5$mrQ#U&nu$?#sxXXW`dyyR7(! z;(7iPSm?0Nbv`42>iyhvQleBpw>A^uvgPuR0sB@#(Ag}xY`?b;BTJR|b;fRt8eHCU z_~{B_n;Djhn~SgpbaOlL9ys>1_Z+M@crSc0f8STTlkd(yJHN>F&jXe7^XKnntxIgB z#FV=EevxOpAR^*=jSaRY`AI{B0X)U6k0F_x9|yo!brkCkkqr(0pO<6km}nSTTO>zd zae`1haZ@=BhsmRaBG`Iuq9m6q;nQ0J#!C2+g*-SP5lSTv3uKGJ70Tmz2AK?&ARL+0 zb_kPTwdVp}wTtUu9xMNHW3`+jl|{kUUUN)%-7RT;q483_VqHvD@+g0##61GeR^;SF zy2MI^@|5n22Chi>A-pzD_-RZy-(n9&G=@pYl`V|rfByZ&-c1&(6`n;LoZw^eigRiC zeU{-;g(S>k0L1)wajH90g=9+Lf=uxV*b0aFzO!~0hbIvTbC66CXTmA|yF>OaDFF)H z+q_?)bHLz{x~v6)<14v#AQF7x7}tGz4XLRu!m&5pH6%DFO9{b+Ehm0&;b z;f0!WrE`iiH)guxvaBwJa}iTf_TBE(>den%h~-&iLUjEj|MLRX3FL>|piXUjR#4On zZa$0Kr`m`V<-=!{5t;EB)v47)0=QTOy8m%*a&8{0*TUf?OuR6KE0ha~4|S@s4fTI5cM594=5CbS|DU)#+ z{7l(k)d9m6%U^2-rWS%(UAM2Y0Mmp`#X1lsKuiGfE=2LzAPvH?O5L!;QAod=02D{p z1gI=c`UM;-3a08-O1ope+Vh0!34JcRPzOa=w|aT@hX(`&HCCF|F3-XTrGOjDs1rI` z=L0V)mVFN)l7B42vAnS%n7CL6T^GQ1j%^;QW-(VWd;JzES|(VW&ZqG=oR-^ysI|u5 z;%!d=87=W-T?P4UP;nI;MHe?jt=vY4+DeSZbSsEj2lbOl^KRKlov{q@24k+nbjzPA z$gdpa13ov@gE*N_Qeq(gc%FOy@Qok=uQokmI2ov6X7o1vixhN>@60!^hNW70>5I-`9~ z!A}d!G0j@sN7rU%MJL1!J#;Z^?57K~=+xMuhuSmM=rDA8y}?j#tdVdM@}RZJhQC&9 znTrKL#Vk=}xs{}F5;#UOg^7F>{-gR1>i)KPvjN+Q4UG3z z$7H|VXC(g@B*KaPn^CU4JKt}5{g?c|-vJD;Mw38Q^>!|pndip)H+Z!E0Af(*G_>Zo z@r~ywiw!_61P);p z!E#pUG80NLS1yOe*`gvQ8bBfsVJT#irE*>_Eau8R#6q4-Dwhfn51v%Klq(l|Dt%Z? zF;^lKz;Yz%&DqdUOlB}5Q^=86i9q@)n$y3D=I1ms4jn0nc?y{jDSBn2j12xjfzAT+ zIWnOVJwfEa@?SgO?2ar%hZ@fkaj+5=F-68UgvFJ|N|e&|u#Cl22*Y?hSdOkuz!k}1 z7Srq3IKf`OKTELJTR8=L{kk}Gc(B*&eM616Zy?a^2A~+#H>D4?%iiI@9sst#!_7sx zz3*^gD0k=`j*D_9-r*!DSN9J0ItLZnXOzlUc=8q2y~3-n@F9xVP`!(){uKw%y0}re z`9cq|+@nA!@!-i2k6f6W?;-N>@bU2WHVB;cF6CCd9@^`uP7dKXe6|a`@F55B+PzX!E2qC%jx4 z%;o|>kAC$3 JEB_yN;2--8G3WpQ literal 0 HcmV?d00001 From 7a1a4e92be117ae522f37d1b0b0a62835d3e5f57 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 5 May 2023 20:03:55 +0200 Subject: [PATCH 15/26] BUGFIX: Default to FirstThunk if OriginalFirstThunk is invalid. --- src/AsmResolver.PE/Imports/SerializedImportedModule.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.PE/Imports/SerializedImportedModule.cs b/src/AsmResolver.PE/Imports/SerializedImportedModule.cs index da74d77c8..4778d5557 100644 --- a/src/AsmResolver.PE/Imports/SerializedImportedModule.cs +++ b/src/AsmResolver.PE/Imports/SerializedImportedModule.cs @@ -73,7 +73,9 @@ protected override IList GetSymbols() ? (0x8000_0000ul, sizeof(uint)) : (0x8000_0000_0000_0000ul, sizeof(ulong)); - if (!_context.File.TryCreateReaderAtRva(_lookupRva, out var lookupItemReader)) + // Prefer OriginalFirstThunk over FirstThunk if it is available and valid. + if (!_context.File.TryCreateReaderAtRva(_lookupRva, out var lookupItemReader) + && !_context.File.TryCreateReaderAtRva(_addressRva, out lookupItemReader)) { _context.BadImage($"Imported module \"{Name}\" has an invalid import lookup thunk table RVA."); return result; @@ -83,16 +85,19 @@ protected override IList GetSymbols() { ImportedSymbol entry; + // Read next thunk data. ulong lookupItem = lookupItemReader.ReadNativeInt(is32Bit); if (lookupItem == 0) break; + // Are we an import by ordinal or by name? if ((lookupItem & ordinalMask) != 0) { entry = new ImportedSymbol((ushort) (lookupItem & 0xFFFF)); } else { + // Resolve hint and name. uint hintNameRva = (uint) (lookupItem & 0xFFFFFFFF); if (!_context.File.TryCreateReaderAtRva(hintNameRva, out var reader)) { From ef4b945db1f59f63a6a4154ad7d0870653e4a842 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 6 May 2023 16:50:36 +0200 Subject: [PATCH 16/26] Rename import reader variables to better reflect their purpose. --- .../Imports/SerializedImportedModule.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/AsmResolver.PE/Imports/SerializedImportedModule.cs b/src/AsmResolver.PE/Imports/SerializedImportedModule.cs index 4778d5557..9be1f5a80 100644 --- a/src/AsmResolver.PE/Imports/SerializedImportedModule.cs +++ b/src/AsmResolver.PE/Imports/SerializedImportedModule.cs @@ -18,8 +18,8 @@ public class SerializedImportedModule : ImportedModule public const uint ModuleImportSize = 5 * sizeof(uint); private readonly PEReaderContext _context; - private readonly uint _lookupRva; - private readonly uint _addressRva; + private readonly uint _originalFirstThunkRva; + private readonly uint _firstThunkRva; /// /// Reads a module import entry from an input stream. @@ -32,11 +32,11 @@ public SerializedImportedModule(PEReaderContext context, ref BinaryStreamReader throw new ArgumentNullException(nameof(reader)); _context = context ?? throw new ArgumentNullException(nameof(context)); - _lookupRva = reader.ReadUInt32(); + _originalFirstThunkRva = reader.ReadUInt32(); TimeDateStamp = reader.ReadUInt32(); ForwarderChain = reader.ReadUInt32(); uint nameRva = reader.ReadUInt32(); - _addressRva = reader.ReadUInt32(); + _firstThunkRva = reader.ReadUInt32(); if (nameRva != 0) { @@ -54,11 +54,11 @@ public SerializedImportedModule(PEReaderContext context, ref BinaryStreamReader /// The PE file format uses an empty module import entry to indicate the end of the list of imported modules. /// public bool IsEmpty => - _lookupRva == 0 + _originalFirstThunkRva == 0 && TimeDateStamp == 0 && ForwarderChain == 0 && Name is null - && _addressRva == 0; + && _firstThunkRva == 0; /// protected override IList GetSymbols() @@ -74,8 +74,8 @@ protected override IList GetSymbols() : (0x8000_0000_0000_0000ul, sizeof(ulong)); // Prefer OriginalFirstThunk over FirstThunk if it is available and valid. - if (!_context.File.TryCreateReaderAtRva(_lookupRva, out var lookupItemReader) - && !_context.File.TryCreateReaderAtRva(_addressRva, out lookupItemReader)) + if (!_context.File.TryCreateReaderAtRva(_originalFirstThunkRva, out var thunkItemReader) + && !_context.File.TryCreateReaderAtRva(_firstThunkRva, out thunkItemReader)) { _context.BadImage($"Imported module \"{Name}\" has an invalid import lookup thunk table RVA."); return result; @@ -86,19 +86,19 @@ protected override IList GetSymbols() ImportedSymbol entry; // Read next thunk data. - ulong lookupItem = lookupItemReader.ReadNativeInt(is32Bit); - if (lookupItem == 0) + ulong thunkItem = thunkItemReader.ReadNativeInt(is32Bit); + if (thunkItem == 0) break; // Are we an import by ordinal or by name? - if ((lookupItem & ordinalMask) != 0) + if ((thunkItem & ordinalMask) != 0) { - entry = new ImportedSymbol((ushort) (lookupItem & 0xFFFF)); + entry = new ImportedSymbol((ushort) (thunkItem & 0xFFFF)); } else { // Resolve hint and name. - uint hintNameRva = (uint) (lookupItem & 0xFFFFFFFF); + uint hintNameRva = (uint) (thunkItem & 0xFFFFFFFF); if (!_context.File.TryCreateReaderAtRva(hintNameRva, out var reader)) { _context.BadImage($"Invalid Hint-Name RVA for import {Name}!#{result.Count.ToString()}."); @@ -110,7 +110,7 @@ protected override IList GetSymbols() } } - entry.AddressTableEntry = _context.File.GetReferenceToRva((uint) (_addressRva + result.Count * pointerSize)); + entry.AddressTableEntry = _context.File.GetReferenceToRva((uint) (_firstThunkRva + result.Count * pointerSize)); result.Add(entry); } From aeeebba01a67d33e251a0f39c5e6d0e2ec6d2039 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 6 May 2023 16:58:41 +0200 Subject: [PATCH 17/26] Rename prefer32Bit -> canLoadAs32Bit. --- src/AsmResolver.DotNet/ModuleDefinition.cs | 6 +++--- .../DotNet/DotNetDirectoryFlagsExtensions.cs | 6 +++--- .../ModuleDefinitionTest.cs | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 72cf9497a..9d7a75e7c 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -802,15 +802,15 @@ public ReferenceImporter DefaultImporter /// Determines whether the module is loaded as a 32-bit process. /// /// true if a 32-bit system should be assumed. - /// true if a 32-bit load is preferred. + /// true if the application can be loaded as a 32-bit process. /// /// true if the module is loaded as a 32-bit process, false if it is loaded as a 64-bit process. /// - public bool IsLoadedAs32Bit(bool assume32BitSystem, bool prefer32Bit) + public bool IsLoadedAs32Bit(bool assume32BitSystem, bool canLoadAs32Bit) { // Assume 32-bit if platform is unknown. return Platform.TryGet(MachineType, out var platform) - && Attributes.IsLoadedAs32Bit(platform, assume32BitSystem, prefer32Bit); + && Attributes.IsLoadedAs32Bit(platform, assume32BitSystem, canLoadAs32Bit); } /// diff --git a/src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs b/src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs index 94c80c7e8..9ed6bb869 100644 --- a/src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs +++ b/src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs @@ -26,11 +26,11 @@ public static bool IsLoadedAs32Bit(this DotNetDirectoryFlags flags, Platform pla /// The flags of the module as specified in its COR20 header. /// The platform to assume the module is loaded on. /// true if a 32-bit system should be assumed. - /// true if a 32-bit load is preferred. + /// true if the application can be loaded as a 32-bit process. /// /// true if the module is loaded as a 32-bit process, false if it is loaded as a 64-bit process. /// - public static bool IsLoadedAs32Bit(this DotNetDirectoryFlags flags, Platform platform, bool assume32BitSystem, bool prefer32Bit) + public static bool IsLoadedAs32Bit(this DotNetDirectoryFlags flags, Platform platform, bool assume32BitSystem, bool canLoadAs32Bit) { // Short-circuit all 64-bit platforms. if (!platform.Is32Bit) @@ -50,7 +50,7 @@ public static bool IsLoadedAs32Bit(this DotNetDirectoryFlags flags, Platform pla // Try cater to preference. if ((flags & DotNetDirectoryFlags.Bit32Preferred) != 0) - return assume32BitSystem | prefer32Bit; + return assume32BitSystem | canLoadAs32Bit; return assume32BitSystem; } diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index b59b4636b..33be5e612 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -524,10 +524,10 @@ public void GetModuleTypeLookalikeNet6() [InlineData(false, true, false)] [InlineData(true, false, true)] [InlineData(true, true, true)] - public void IsLoadedAs32BitAnyCPUModule(bool assume32Bit, bool prefer32Bit, bool expected) + public void IsLoadedAs32BitAnyCPUModule(bool assume32Bit, bool canLoadAs32Bit, bool expected) { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); - Assert.Equal(expected, module.IsLoadedAs32Bit(assume32Bit, prefer32Bit)); + Assert.Equal(expected, module.IsLoadedAs32Bit(assume32Bit, canLoadAs32Bit)); } [Theory] @@ -535,11 +535,11 @@ public void IsLoadedAs32BitAnyCPUModule(bool assume32Bit, bool prefer32Bit, bool [InlineData(false, true, true)] [InlineData(true, false, true)] [InlineData(true, true, true)] - public void IsLoadedAs32BitAnyCPUModulePrefer32Bit(bool assume32Bit, bool prefer32Bit, bool expected) + public void IsLoadedAs32BitAnyCPUModulePrefer32Bit(bool assume32Bit, bool canLoadAs32Bit, bool expected) { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); module.IsBit32Preferred = true; - Assert.Equal(expected, module.IsLoadedAs32Bit(assume32Bit, prefer32Bit)); + Assert.Equal(expected, module.IsLoadedAs32Bit(assume32Bit, canLoadAs32Bit)); } [Theory] @@ -547,11 +547,11 @@ public void IsLoadedAs32BitAnyCPUModulePrefer32Bit(bool assume32Bit, bool prefer [InlineData(false, true)] [InlineData(true, false)] [InlineData(true, true)] - public void IsLoadedAs32Bit64BitModule(bool assume32Bit, bool prefer32Bit) + public void IsLoadedAs32Bit64BitModule(bool assume32Bit, bool canLoadAs32Bit) { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); module.MachineType = MachineType.Amd64; - Assert.False(module.IsLoadedAs32Bit(assume32Bit, prefer32Bit)); + Assert.False(module.IsLoadedAs32Bit(assume32Bit, canLoadAs32Bit)); } [Theory] @@ -559,12 +559,12 @@ public void IsLoadedAs32Bit64BitModule(bool assume32Bit, bool prefer32Bit) [InlineData(false, true)] [InlineData(true, false)] [InlineData(true, true)] - public void IsLoadedAs32Bit32BitModule(bool assume32Bit, bool prefer32Bit) + public void IsLoadedAs32Bit32BitModule(bool assume32Bit, bool canLoadAs32Bit) { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); module.MachineType = MachineType.I386; module.IsBit32Required = true; - Assert.True(module.IsLoadedAs32Bit(assume32Bit, prefer32Bit)); + Assert.True(module.IsLoadedAs32Bit(assume32Bit, canLoadAs32Bit)); } } } From 687f46fd1673354c5aba1d4ce493b3b685fb8c6b Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 6 May 2023 17:20:25 +0200 Subject: [PATCH 18/26] Add ManagedPEImageBuilder::ErrorListener to allow for better DI. --- .../Builder/ManagedPEImageBuilder.cs | 34 +++- .../Builder/PEImageBuildContext.cs | 86 +++++----- .../Builder/PEImageBuildResult.cs | 147 +++++++++--------- src/AsmResolver.DotNet/ModuleDefinition.cs | 21 ++- 4 files changed, 159 insertions(+), 129 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs index e16ff3478..d918ba2a0 100644 --- a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs +++ b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs @@ -31,11 +31,30 @@ public ManagedPEImageBuilder(MetadataBuilderFlags metadataBuilderFlags) /// /// Creates a new instance of the class, using the provided - /// .NET data directory flags. + /// .NET data directory factory. /// public ManagedPEImageBuilder(IDotNetDirectoryFactory factory) + : this(factory, new DiagnosticBag()) { - DotNetDirectoryFactory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Creates a new instance of the class, using the provided + /// .NET data directory factory. + /// + public ManagedPEImageBuilder(IErrorListener errorListener) + : this(new DotNetDirectoryFactory(), errorListener) + { + } + + /// + /// Creates a new instance of the class, using the provided + /// .NET data directory factory and error listener. + /// + public ManagedPEImageBuilder(IDotNetDirectoryFactory factory, IErrorListener errorListener) + { + DotNetDirectoryFactory = factory; + ErrorListener = errorListener; } /// @@ -47,10 +66,19 @@ public IDotNetDirectoryFactory DotNetDirectoryFactory set; } + /// + /// Gets or sets the object responsible for keeping track of diagnostics during the building process. + /// + public IErrorListener ErrorListener + { + get; + set; + } + /// public PEImageBuildResult CreateImage(ModuleDefinition module) { - var context = new PEImageBuildContext(); + var context = new PEImageBuildContext(ErrorListener); PEImage? image = null; ITokenMapping? tokenMapping = null; diff --git a/src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs b/src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs index aa41ef1e9..b56dd6b26 100644 --- a/src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs +++ b/src/AsmResolver.DotNet/Builder/PEImageBuildContext.cs @@ -1,54 +1,50 @@ -using System; - -namespace AsmResolver.DotNet.Builder -{ - /// - /// Provides a context in which a PE image construction takes place in. - /// - public class PEImageBuildContext - { - /// - /// Creates a new empty build context. - /// - public PEImageBuildContext() - { - ErrorListener = new DiagnosticBag(); +using System; + +namespace AsmResolver.DotNet.Builder +{ + /// + /// Provides a context in which a PE image construction takes place in. + /// + public class PEImageBuildContext + { + /// + /// Creates a new empty build context. + /// + public PEImageBuildContext() + { + ErrorListener = new DiagnosticBag(); } - /// - /// Creates a new build context. - /// + /// + /// Creates a new build context. + /// /// The diagnostic bag to use. - [Obsolete] - public PEImageBuildContext(DiagnosticBag diagnosticBag) - { - ErrorListener = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag)); + public PEImageBuildContext(DiagnosticBag diagnosticBag) + { + ErrorListener = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag)); } - /// - /// Creates a new build context. - /// - /// The diagnostic bag to use. - public PEImageBuildContext(IErrorListener errorListener) - { - ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); + /// + /// Creates a new build context. + /// + /// The diagnostic bag to use. + public PEImageBuildContext(IErrorListener errorListener) + { + ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); } - /// - /// Gets the bag that collects all diagnostic information during the building process. + /// + /// Gets the bag that collects all diagnostic information during the building process. /// - [Obsolete] - public DiagnosticBag DiagnosticBag - { - get => (DiagnosticBag)ErrorListener; - } + [Obsolete("Use ErrorListener instead.")] + public DiagnosticBag? DiagnosticBag => ErrorListener as DiagnosticBag; - /// - /// Gets the error listener that handles all diagnostic information during the building process. - /// - public IErrorListener ErrorListener - { - get; - } - } -} + /// + /// Gets the error listener that handles all diagnostic information during the building process. + /// + public IErrorListener ErrorListener + { + get; + } + } +} diff --git a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs index bf7daad24..928614a5a 100644 --- a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs +++ b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs @@ -1,78 +1,75 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using AsmResolver.PE; - -namespace AsmResolver.DotNet.Builder -{ - /// - /// Describes the result of the construction of a from a . - /// - public class PEImageBuildResult - { - /// - /// Creates a new instance of the class. - /// - /// The constructed image, or null if the construction failed. - /// The diagnostics that were collected during the construction of the image. +using System; +using System.Diagnostics.CodeAnalysis; +using AsmResolver.PE; + +namespace AsmResolver.DotNet.Builder +{ + /// + /// Describes the result of the construction of a from a . + /// + public class PEImageBuildResult + { + /// + /// Creates a new instance of the class. + /// + /// The constructed image, or null if the construction failed. + /// The diagnostics that were collected during the construction of the image. + /// An object that maps metadata members to their newly assigned tokens. + [Obsolete] + public PEImageBuildResult(IPEImage? image, DiagnosticBag diagnosticBag, ITokenMapping tokenMapping) + { + ConstructedImage = image; + ErrorListener = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag)); + TokenMapping = tokenMapping ?? throw new ArgumentNullException(nameof(tokenMapping)); + } + + /// + /// Creates a new instance of the class. + /// + /// The constructed image, or null if the construction failed. + /// The diagnostics that were collected during the construction of the image. /// An object that maps metadata members to their newly assigned tokens. - [Obsolete] - public PEImageBuildResult(IPEImage? image, DiagnosticBag diagnosticBag, ITokenMapping tokenMapping) - { - ConstructedImage = image; - ErrorListener = diagnosticBag ?? throw new ArgumentNullException(nameof(diagnosticBag)); - TokenMapping = tokenMapping ?? throw new ArgumentNullException(nameof(tokenMapping)); + public PEImageBuildResult(IPEImage? image, IErrorListener errorListener, ITokenMapping tokenMapping) + { + ConstructedImage = image; + ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); + TokenMapping = tokenMapping ?? throw new ArgumentNullException(nameof(tokenMapping)); + } + + /// + /// Gets the constructed image, or null if the construction failed. + /// + public IPEImage? ConstructedImage + { + get; } - - /// - /// Creates a new instance of the class. - /// - /// The constructed image, or null if the construction failed. - /// The diagnostics that were collected during the construction of the image. - /// An object that maps metadata members to their newly assigned tokens. - public PEImageBuildResult(IPEImage? image, IErrorListener errorListener, ITokenMapping tokenMapping) - { - ConstructedImage = image; - ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); - TokenMapping = tokenMapping ?? throw new ArgumentNullException(nameof(tokenMapping)); - } - - /// - /// Gets the constructed image, or null if the construction failed. - /// - public IPEImage? ConstructedImage - { - get; - } - - /// - /// Gets a value indicating whether the image was constructed successfully or not. - /// - [MemberNotNullWhen(false, nameof(ConstructedImage))] - public bool HasFailed => ConstructedImage is null; - - /// - /// Gets the bag containing the diagnostics that were collected during the construction of the image. + + /// + /// Gets a value indicating whether the image was constructed successfully or not. /// - [Obsolete] - public DiagnosticBag DiagnosticBag - { - get=> (DiagnosticBag)ErrorListener; - } - - /// - /// Gets the error listener handling the diagnostics that were collected during the construction of the image. - /// - public IErrorListener ErrorListener - { - get; - } - - /// - /// Gets an object that maps metadata members to their newly assigned tokens. - /// - public ITokenMapping TokenMapping - { - get; - } - } -} + [MemberNotNullWhen(false, nameof(ConstructedImage))] + public bool HasFailed => ConstructedImage is null; + + /// + /// Gets the bag containing the diagnostics that were collected during the construction of the image (if available). + /// + [Obsolete("Use the ErrorListener property instead.")] + public DiagnosticBag? DiagnosticBag => ErrorListener as DiagnosticBag; + + /// + /// Gets the error listener handling the diagnostics that were collected during the construction of the image. + /// + public IErrorListener ErrorListener + { + get; + } + + /// + /// Gets an object that maps metadata members to their newly assigned tokens. + /// + public ITokenMapping TokenMapping + { + get; + } + } +} diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index ddbe0e298..d347007de 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -1291,20 +1291,29 @@ public void Write(IBinaryStreamWriter writer, IPEImageBuilder imageBuilder, IPEF /// /// The engine to use for reconstructing a PE image. /// IPEImage built by the specified IPEImageBuilder - /// Occurs when the construction of the image threw exceptions. + /// + /// Occurs when the construction of the image threw exceptions, and the used error listener is an instance of + /// a . + /// + /// + /// Occurs when the construction of the PE image failed completely. + /// public IPEImage ToPEImage(IPEImageBuilder imageBuilder) { var result = imageBuilder.CreateImage(this); - if (result.ErrorListener is DiagnosticBag diagnosticBag && diagnosticBag.HasErrors) + + // If the error listener is a diagnostic bag, we can pull out the exceptions that were thrown. + if (result.ErrorListener is DiagnosticBag {HasErrors: true} diagnosticBag) { throw new AggregateException( "Construction of the PE image failed with one or more errors.", diagnosticBag.Exceptions); } - else if (result.HasFailed) - { - throw new Exception("Construction of the PE image failed."); - } + + // If we still failed but we don't have special handling for the provided error listener, just throw a + // simple exception instead. + if (result.HasFailed) + throw new MetadataBuilderException("Construction of the PE image failed."); return result.ConstructedImage; } From 01764bcc668a07b162ce5bb7d0c3f25c474baee2 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 6 May 2023 17:37:14 +0200 Subject: [PATCH 19/26] Normalize some tests with new builder API. --- .../TokenPreservation/TokenPreservationTestBase.cs | 10 ++++------ .../Code/Cil/CilMethodBodyTest.cs | 5 +---- test/AsmResolver.DotNet.Tests/ConstantTest.cs | 3 --- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs index ff2046e00..1ca3a70f5 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs @@ -32,17 +32,15 @@ protected static List GetMembers(ModuleDefinition module, Tabl protected static ModuleDefinition RebuildAndReloadModule(ModuleDefinition module, MetadataBuilderFlags builderFlags) { - var builder = new ManagedPEImageBuilder - { - DotNetDirectoryFactory = new DotNetDirectoryFactory(builderFlags) - }; + var builder = new ManagedPEImageBuilder(builderFlags); var result = builder.CreateImage(module); if (result.HasFailed) + { if (result.ErrorListener is DiagnosticBag diagnosticBag) throw new AggregateException(diagnosticBag.Exceptions); - else - throw new Exception("Image creation failed."); + throw new Exception("Image creation failed."); + } var newImage = result.ConstructedImage; diff --git a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs index a3626ba04..5102359aa 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs @@ -32,10 +32,7 @@ private static CilMethodBody GetMethodBodyInModule(ModuleDefinition module, stri private CilMethodBody RebuildAndLookup(CilMethodBody methodBody) { - var module = methodBody.Owner.Module; - - string tempFile = Path.GetTempFileName(); - module.Write(tempFile); + var module = methodBody.Owner.Module!; var stream = new MemoryStream(); module.Write(stream); diff --git a/test/AsmResolver.DotNet.Tests/ConstantTest.cs b/test/AsmResolver.DotNet.Tests/ConstantTest.cs index 1fd9770d8..d455f2cdd 100644 --- a/test/AsmResolver.DotNet.Tests/ConstantTest.cs +++ b/test/AsmResolver.DotNet.Tests/ConstantTest.cs @@ -21,9 +21,6 @@ private static Constant GetFieldConstantInModule(ModuleDefinition module, string private Constant RebuildAndLookup(ModuleDefinition module, string name) { - string tempFile = Path.GetTempFileName(); - module.Write(tempFile); - var stream = new MemoryStream(); module.Write(stream); From 9ed4ebe09b51765126d794830ee77401e4f6e303 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 6 May 2023 17:37:24 +0200 Subject: [PATCH 20/26] Update docs on new image builder API. --- docs/dotnet/advanced-pe-image-building.rst | 51 +++++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/docs/dotnet/advanced-pe-image-building.rst b/docs/dotnet/advanced-pe-image-building.rst index db94d483c..c858333f8 100644 --- a/docs/dotnet/advanced-pe-image-building.rst +++ b/docs/dotnet/advanced-pe-image-building.rst @@ -162,6 +162,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 ------------------- @@ -194,26 +201,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 + + 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.DiagnosticBag.IsFatal) + if (!result.HasFailed) { var fileBuilder = new ManagedPEFileBuilder(); var file = fileBuilder.CreateFile(result.ConstructedImage); file.Write("output.exe"); - } \ No newline at end of file + } + + From 276f6e61763449ea67166f9f4859eca578a67e78 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 7 May 2023 15:56:00 +0200 Subject: [PATCH 21/26] Add ToPEImage() overload that allows for ignoring non-fatal errors. --- docs/dotnet/advanced-pe-image-building.rst | 28 +++++++++++++++++-- src/AsmResolver.DotNet/ModuleDefinition.cs | 23 +++++++++++++-- .../Builder/ManagedPEImageBuilderTest.cs | 27 ++++++++++++++++++ .../ModuleDefinitionTest.cs | 1 - 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/docs/dotnet/advanced-pe-image-building.rst b/docs/dotnet/advanced-pe-image-building.rst index c858333f8..8a5801013 100644 --- a/docs/dotnet/advanced-pe-image-building.rst +++ b/docs/dotnet/advanced-pe-image-building.rst @@ -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 @@ -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 @@ -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... */ diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index d347007de..209a23a17 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -1284,7 +1284,7 @@ public void Write(IBinaryStreamWriter writer, IPEImageBuilder imageBuilder, IPEF /// /// IPEImage built using by default /// Occurs when the construction of the image threw exceptions. - public IPEImage ToPEImage() => ToPEImage(new ManagedPEImageBuilder()); + public IPEImage ToPEImage() => ToPEImage(new ManagedPEImageBuilder(), true); /// /// Rebuilds the .NET module to a portable executable file and returns the IPEImage. @@ -1298,12 +1298,29 @@ public void Write(IBinaryStreamWriter writer, IPEImageBuilder imageBuilder, IPEF /// /// Occurs when the construction of the PE image failed completely. /// - public IPEImage ToPEImage(IPEImageBuilder imageBuilder) + public IPEImage ToPEImage(IPEImageBuilder imageBuilder) => ToPEImage(imageBuilder, true); + + /// + /// Rebuilds the .NET module to a portable executable file and returns the IPEImage. + /// + /// The engine to use for reconstructing a PE image. + /// + /// true if non-fatal errors should be thrown as an exception, false otherwise. + /// + /// IPEImage built by the specified IPEImageBuilder + /// + /// Occurs when the construction of the image threw exceptions, and the used error listener is an instance of + /// a . + /// + /// + /// Occurs when the construction of the PE image failed completely. + /// + public IPEImage ToPEImage(IPEImageBuilder imageBuilder, bool throwOnNonFatalError) { var result = imageBuilder.CreateImage(this); // If the error listener is a diagnostic bag, we can pull out the exceptions that were thrown. - if (result.ErrorListener is DiagnosticBag {HasErrors: true} diagnosticBag) + if (result.ErrorListener is DiagnosticBag {HasErrors: true} diagnosticBag && throwOnNonFatalError) { throw new AggregateException( "Construction of the PE image failed with one or more errors.", diff --git a/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs b/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs index 82c2630e5..942981055 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs @@ -1,8 +1,11 @@ +using System; using System.IO; using System.Linq; using AsmResolver.DotNet.Builder; +using AsmResolver.DotNet.Builder.Metadata; using AsmResolver.PE; using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; namespace AsmResolver.DotNet.Tests.Builder @@ -147,5 +150,29 @@ public void PreserveUnknownStreamsAndStreamOrder() newImage.DotNetDirectory!.Metadata!.GetStream("#Custom")); Assert.Equal(data, Assert.IsAssignableFrom(newStream.Contents).ToArray()); } + + [Fact] + public void BuildInvalidImageShouldRegisterDiagnostics() + { + // Prepare temp assembly. + var assembly = new AssemblyDefinition("Assembly", new Version(1, 0, 0, 0)); + var module = new ModuleDefinition("Module"); + assembly.Modules.Add(module); + + // Add some field with an non-imported field type. + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "Field", + FieldAttributes.Static, + new TypeReference(null, "NonImportedNamespace", "NonImportedType").ToTypeSignature())); + + // Build. + var bag = new DiagnosticBag(); + var image = module.ToPEImage(new ManagedPEImageBuilder(bag), false); + + // Verify diagnostics. + Assert.NotNull(image); + Assert.Contains(bag.Exceptions, x => x is MemberNotImportedException); + } + } } diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index db9a460ae..27621415a 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using AsmResolver.DotNet.Builder; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.IO; From 394d285b10777237d635f4d75c5a297b650024aa Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 7 May 2023 16:52:28 +0200 Subject: [PATCH 22/26] Bump version --- Directory.Build.props | 2 +- appveyor.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a1f4f6d97..614e3282c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 5.2.0 + 5.3.0 diff --git a/appveyor.yml b/appveyor.yml index 7d1c9f32d..81316e3d6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 5.2.0-master-build.{build} + version: 5.3.0-master-build.{build} configuration: Release skip_commits: @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 5.2.0-dev-build.{build} + version: 5.3.0-dev-build.{build} configuration: Release skip_commits: From 48aadf6fdddb97c552256775f51d31fe1704ca1e Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 8 May 2023 17:18:54 +0200 Subject: [PATCH 23/26] Initial migration to docfx. --- docs/.gitignore | 10 + docs/articles/core/segments.md | 252 ++++++++++++ .../dotnet/advanced-module-reading.md | 170 ++++++++ .../dotnet/advanced-pe-image-building.md | 315 +++++++++++++++ docs/articles/dotnet/basics.md | 157 ++++++++ docs/articles/dotnet/bundles.md | 237 ++++++++++++ docs/articles/dotnet/cloning.md | 304 +++++++++++++++ docs/articles/dotnet/dynamic-methods.md | 72 ++++ docs/articles/dotnet/importing.md | 209 ++++++++++ docs/articles/dotnet/index.md | 22 ++ docs/articles/dotnet/managed-method-bodies.md | 345 +++++++++++++++++ docs/articles/dotnet/managed-resources.md | 266 +++++++++++++ docs/articles/dotnet/member-tree.md | 123 ++++++ docs/articles/dotnet/token-allocation.md | 43 +++ docs/articles/dotnet/type-memory-layout.md | 141 +++++++ docs/articles/dotnet/type-signatures.md | 280 ++++++++++++++ .../dotnet/unmanaged-method-bodies.md | 244 ++++++++++++ docs/articles/faq.md | 96 +++++ docs/articles/index.md | 8 + docs/articles/overview.md | 24 ++ docs/articles/pdb/basics.md | 105 +++++ docs/articles/pdb/index.md | 23 ++ docs/articles/pdb/symbols.md | 113 ++++++ docs/articles/pefile/basics.md | 82 ++++ docs/articles/pefile/headers.md | 223 +++++++++++ docs/articles/pefile/index.md | 15 + docs/articles/pefile/sections.md | 123 ++++++ docs/articles/peimage/advanced-pe-reading.md | 138 +++++++ docs/articles/peimage/basics.md | 117 ++++++ docs/articles/peimage/debug.md | 69 ++++ docs/articles/peimage/dotnet.md | 343 +++++++++++++++++ docs/articles/peimage/exceptions.md | 64 +++ docs/articles/peimage/exports.md | 46 +++ docs/articles/peimage/imports.md | 81 ++++ docs/articles/peimage/index.md | 7 + docs/articles/peimage/pe-building.md | 194 ++++++++++ docs/articles/peimage/tls.md | 82 ++++ docs/articles/peimage/win32resources.md | 153 ++++++++ docs/articles/toc.yml | 93 +++++ docs/core/segments.rst | 250 ------------ docs/docfx.json | 58 +++ docs/dotnet/advanced-module-reading.rst | 156 -------- docs/dotnet/advanced-pe-image-building.rst | 280 -------------- docs/dotnet/basics.rst | 153 -------- docs/dotnet/bundles.rst | 202 ---------- docs/dotnet/cloning.rst | 263 ------------- docs/dotnet/dynamic-methods.rst | 63 --- docs/dotnet/importing.rst | 203 ---------- docs/dotnet/index.rst | 14 - docs/dotnet/managed-method-bodies.rst | 310 --------------- docs/dotnet/managed-resources.rst | 239 ------------ docs/dotnet/member-tree.rst | 115 ------ docs/dotnet/token-allocation.rst | 30 -- docs/dotnet/type-memory-layout.rst | 127 ------ docs/dotnet/type-signatures.rst | 288 -------------- docs/dotnet/unmanaged-method-bodies.rst | 205 ---------- docs/faq.rst | 60 --- docs/index.md | 4 + docs/index.rst | 85 ---- docs/my-template/public/main.css | 22 ++ docs/overview.rst | 12 - docs/pdb/basics.rst | 113 ------ docs/pdb/index.rst | 14 - docs/pdb/symbols.rst | 108 ------ docs/pefile/basics.rst | 80 ---- docs/pefile/headers.rst | 218 ----------- docs/pefile/index.rst | 9 - docs/pefile/sections.rst | 119 ------ docs/peimage/advanced-pe-reading.rst | 121 ------ docs/peimage/basics.rst | 114 ------ docs/peimage/debug.rst | 59 --- docs/peimage/dotnet.rst | 363 ------------------ docs/peimage/exceptions.rst | 56 --- docs/peimage/exports.rst | 35 -- docs/peimage/imports.rst | 68 ---- docs/peimage/index.rst | 4 - docs/peimage/pe-building.rst | 173 --------- docs/peimage/tls.rst | 67 ---- docs/peimage/win32resources.rst | 146 ------- docs/toc.yml | 5 + 80 files changed, 5478 insertions(+), 4922 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/articles/core/segments.md create mode 100644 docs/articles/dotnet/advanced-module-reading.md create mode 100644 docs/articles/dotnet/advanced-pe-image-building.md create mode 100644 docs/articles/dotnet/basics.md create mode 100644 docs/articles/dotnet/bundles.md create mode 100644 docs/articles/dotnet/cloning.md create mode 100644 docs/articles/dotnet/dynamic-methods.md create mode 100644 docs/articles/dotnet/importing.md create mode 100644 docs/articles/dotnet/index.md create mode 100644 docs/articles/dotnet/managed-method-bodies.md create mode 100644 docs/articles/dotnet/managed-resources.md create mode 100644 docs/articles/dotnet/member-tree.md create mode 100644 docs/articles/dotnet/token-allocation.md create mode 100644 docs/articles/dotnet/type-memory-layout.md create mode 100644 docs/articles/dotnet/type-signatures.md create mode 100644 docs/articles/dotnet/unmanaged-method-bodies.md create mode 100644 docs/articles/faq.md create mode 100644 docs/articles/index.md create mode 100644 docs/articles/overview.md create mode 100644 docs/articles/pdb/basics.md create mode 100644 docs/articles/pdb/index.md create mode 100644 docs/articles/pdb/symbols.md create mode 100644 docs/articles/pefile/basics.md create mode 100644 docs/articles/pefile/headers.md create mode 100644 docs/articles/pefile/index.md create mode 100644 docs/articles/pefile/sections.md create mode 100644 docs/articles/peimage/advanced-pe-reading.md create mode 100644 docs/articles/peimage/basics.md create mode 100644 docs/articles/peimage/debug.md create mode 100644 docs/articles/peimage/dotnet.md create mode 100644 docs/articles/peimage/exceptions.md create mode 100644 docs/articles/peimage/exports.md create mode 100644 docs/articles/peimage/imports.md create mode 100644 docs/articles/peimage/index.md create mode 100644 docs/articles/peimage/pe-building.md create mode 100644 docs/articles/peimage/tls.md create mode 100644 docs/articles/peimage/win32resources.md create mode 100644 docs/articles/toc.yml delete mode 100644 docs/core/segments.rst create mode 100644 docs/docfx.json delete mode 100644 docs/dotnet/advanced-module-reading.rst delete mode 100644 docs/dotnet/advanced-pe-image-building.rst delete mode 100644 docs/dotnet/basics.rst delete mode 100644 docs/dotnet/bundles.rst delete mode 100644 docs/dotnet/cloning.rst delete mode 100644 docs/dotnet/dynamic-methods.rst delete mode 100644 docs/dotnet/importing.rst delete mode 100644 docs/dotnet/index.rst delete mode 100644 docs/dotnet/managed-method-bodies.rst delete mode 100644 docs/dotnet/managed-resources.rst delete mode 100644 docs/dotnet/member-tree.rst delete mode 100644 docs/dotnet/token-allocation.rst delete mode 100644 docs/dotnet/type-memory-layout.rst delete mode 100644 docs/dotnet/type-signatures.rst delete mode 100644 docs/dotnet/unmanaged-method-bodies.rst delete mode 100644 docs/faq.rst create mode 100644 docs/index.md delete mode 100644 docs/index.rst create mode 100644 docs/my-template/public/main.css delete mode 100644 docs/overview.rst delete mode 100644 docs/pdb/basics.rst delete mode 100644 docs/pdb/index.rst delete mode 100644 docs/pdb/symbols.rst delete mode 100644 docs/pefile/basics.rst delete mode 100644 docs/pefile/headers.rst delete mode 100644 docs/pefile/index.rst delete mode 100644 docs/pefile/sections.rst delete mode 100644 docs/peimage/advanced-pe-reading.rst delete mode 100644 docs/peimage/basics.rst delete mode 100644 docs/peimage/debug.rst delete mode 100644 docs/peimage/dotnet.rst delete mode 100644 docs/peimage/exceptions.rst delete mode 100644 docs/peimage/exports.rst delete mode 100644 docs/peimage/imports.rst delete mode 100644 docs/peimage/index.rst delete mode 100644 docs/peimage/pe-building.rst delete mode 100644 docs/peimage/tls.rst delete mode 100644 docs/peimage/win32resources.rst create mode 100644 docs/toc.yml diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..f6cdee1d7 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,10 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ +_site/ +api/ diff --git a/docs/articles/core/segments.md b/docs/articles/core/segments.md new file mode 100644 index 000000000..4473c31d3 --- /dev/null +++ b/docs/articles/core/segments.md @@ -0,0 +1,252 @@ +# Reading and Writing File Segments + +Segments are the basis of everything in AsmResolver. They are the +fundamental building blocks that together make up a binary file (such as +a PE file). Segments are organized as a tree, where the leaves are +single contiguous chunk of memory, while the nodes are segments that +comprise multiple smaller sub-segments. The aim of segments is to +abstract away the complicated mess that comes with calculating offsets, +sizes and updating them accordingly, allowing programmers to easily read +binary files, as well as construct new ones. + +Every class that directly translates to a concrete segment in a file on +the disk implements the `ISegment` interface. In the following, some of +the basics of `ISegment` as well as common examples will be introduced. + +## Basic Data Segments + +The simplest and arguably the most commonly used form of segment is the +`DataSegment` class. This is a class that wraps around a `byte[]` into +an instance of `ISegment`, allowing it to be used in any context where a +segment are expected in AsmResolver. + +``` csharp +byte[] data = new byte[] { 1, 2, 3, 4 }; +var segment = new DataSegment(data); +``` + +While the name of the `DataSegment` class implies it is used for +defining literal data (such as a constant for a variable), it can be +used to define *any* type of contiguous memory. This also includes a raw +code stream of a function body and sometimes entire program sections. + +## Reading Segment Contents + +Some implementations of `ISegment` (such as `DataSegment`) allow for +reading binary data directly. Segments that allow for this implement +`IReadableSegment`, which defines a function `CreateReader` that can be +used to create an instance of `BinaryStreamReader` that starts at the +beginning of the raw contents of the segment. This reader can then be +used to read the contents of the segment. + +``` csharp +byte[] data = new byte[] { 1, 2, 3, 4 }; +IReadableSegment segment = new DataSegment(data); + +var reader = segment.CreateReader(); +reader.ReadByte(); // returns 1 +reader.ReadByte(); // returns 2 +reader.ReadByte(); // returns 3 +reader.ReadByte(); // returns 4 +reader.ReadByte(); // throws EndOfStreamException. +``` + +Alternatively, a `IReadableSegment` can be turned into a `byte[]` +quickly using the `ToArray()` method. + +``` csharp +byte[] data = new byte[] { 1, 2, 3, 4 }; +IReadableSegment segment = new DataSegment(data); + +byte[] allData = segment.ToArray(); // Returns { 1, 2, 3, 4 } +``` + +## Composing new Segments + +Many segments comprise multiple smaller sub-segments. For example, PE +sections often do not contain just a single data structure, but are a +collection of structures concatenated together. To facilitate more +complicated structures like these, the `SegmentBuilder` class can be +used to combine `ISegment` instances into one effortlessly: + +``` csharp +var builder = new SegmentBuilder(); + +builder.Add(new DataSegment(...)); +builder.Add(new DataSegment(...)); +``` + +Many segments in an executable file format require segments to be +aligned to a certain byte-boundary. The `SegmentBuilder::Add` method +allows for specifying this alignment, and automatically adjust the +offsets and sizes accordingly: + +``` csharp +var builder = new SegmentBuilder(); + +// Add some segment with potentially a size that is not a multiple of 4 bytes. +builder.Add(new DataSegment(...)); + +// Ensure the next segment is aligned to a 4-byte boundary in the final file. +builder.Add(new DataSegment(...), alignment: 4); +``` + +Since `SegmentBuilder` implements `ISegment` itself, it can also be used +within another `SegmentBuilder`, allowing for recursive constructions +like the following: + +``` csharp +var child = new SegmentBuilder(); +child.Add(new DataSegment(...)); +child.Add(new DataSegment(...)); + +var root = new SegmentBuilder(); +root.Add(new DataSegment(...)); +root.Add(child); // Nest segment builders into each other. +``` + +## Resizing Segments at Runtime + +Most segments in an executable file retain their size at runtime. +However, some segments (such as a `.bss` section in a PE file) may be +resized upon mapping it into memory. AsmResolver represents these +segments using the `VirtualSegment` class: + +``` csharp +var physicalContents = new DataSegment(new byte[] {1, 2, 3, 4}); +section.Contents = new VirtualSegment(physicalContents, 0x1000); // Create a new segment with a virtual size of 0x1000 bytes. +``` + +## Patching Segments + +Some use-cases of AsmResolver require segments to be hot-patched with +new data after serialization. This is done via the `PatchedSegment` +class. + +Any segment can be wrapped into a `PatchedSegment` via its constructor: + +``` csharp +using AsmResolver.Patching; + +ISegment segment = ... +var patchedSegment = new PatchedSegment(segment); +``` + +Alternatively, you can use (the preferred) fluent syntax: + +``` csharp +using AsmResolver.Patching; + +ISegment segment = ... +var patchedSegment = segment.AsPatchedSegment(); +``` + +Applying the patches can then be done by repeatedly calling one of the +`Patch` method overloads. Below is an example of patching a section +within a PE file: + +``` csharp +var peFile = PEFile.FromFile("input.exe"); +var section = peFile.Sections.First(s => s.Name == ".text"); + +var someSymbol = peImage + .Imports.First(m => m.Name == "ucrtbased.dll") + .Symbols.First(s => s.Name == "puts"); + +section.Contents = section.Contents.AsPatchedSegment() // Create patched segment. + .Patch(offset: 0x10, data: new byte[] {1, 2, 3, 4}) // Apply literal bytes patch + .Patch(offset: 0x20, AddressFixupType.Absolute64BitAddress, someSymbol); // Apply address fixup patch. +``` + +The patching API can be extended by implementing the `IPatch` yourself. + +## Calculating Offsets and Sizes + +Typically, the `ISegment` API aims to abstract away any raw offset, +relative virtual address (RVA), and/or size of a data structure within a +binary file. However, in case the final offset and/or size of a segment +still need to be determined and used (e.g., when implementing new +segments), it is important to understand how this is done. + +Two properties are responsible for representing the offsets: + +- `Offset`: The starting file or memory address of the segment. +- `Rva`: The virtual address of the segment, relative to the + executable\'s image base at runtime. + +Typically, these properties are read-only and managed by AsmResolver +itself. However, to update the offsets and RVAs of a segment, you can +call the `UpdateOffsets` method. This method traverses the entire +segment recursively, and updates the offsets accordingly. + +``` csharp +ISegment segment = ... + +// Relocate a segment to an offsets-rva pair: +segment.UpdateOffsets(new RelocationParameters(offset: 0x200, rva: 0x2000); + +Console.WriteLine("Offset: 0x{0:X8}", segment.Offset); // Prints 0x200 +Console.WriteLine("Rva: 0x{0:X8}", segment.Rva); // Prints 0x2000 +``` + +> [!WARNING] +> Try to call `UpdateOffsets()` as sparsely as possible. The method does a +> full pass on the entire segment, and updates all offsets of all +> sub-segments as well. It can thus be very inefficient to call them +> repeatedly. + +The size (in bytes) of a segment can be calculated using either the +`GetPhysicalSize()` or `GetVirtualSize()`. Typically, these two +measurements are going to be equal, but for some segments (such as a +`VirtualSegment`) this may differ: + +``` csharp +ISegment segment = ... + +// Measure the size of the segment: +uint physicalSize = segment.GetPhysicalSize(); +uint virtualSize = segment.GetVirtualSize(); + +Console.WriteLine("Physical (File) Size: 0x{0:X8}", physicalSize); +Console.WriteLine("Virtual (Runtime) Size: 0x{0:X8}", virtualSize); +``` + +> [!WARNING] +> Only call `GetPhysicalSize()` and `GetVirtualSize()` whenever you know +> the offsets of the segment are up to date. Due to padding requirements, +> many segments will have a slightly different size depending on the final +> file offset they are placed at. + +> [!WARNING] +> Try to call `GetPhysicalSize()` and `GetVirtualSize()` as sparsely as +> possible. These methods do a full pass on the entire segment, and +> measure the total amount of bytes required to represent it. It can thus +> be very inefficient to call them repeatedly. + + +## Serializing Segments + +Segments are serialized using the `ISegment::Write` method. + +``` csharp +ISegment segment = ... + +using var stream = new MemoryStream(); +segment.Write(new BinaryStreamWriter(stream)); + +byte[] serializedData = stream.ToArray(); +``` + +Alternatively, you can quickly serialize a segment to a `byte[]` using +the `WriteIntoArray()` extension method: + +``` csharp +ISegment segment = ... + +byte[] serializedData = stream.WriteIntoArray(); +``` + +> [!WARNING] +> Only call `Write` whenever you know the offsets of the segment are up to +> date. Many segments will contain offsets to other segments in the file, +> which may not be accurate until all offsets are calculated. diff --git a/docs/articles/dotnet/advanced-module-reading.md b/docs/articles/dotnet/advanced-module-reading.md new file mode 100644 index 000000000..959509b4a --- /dev/null +++ b/docs/articles/dotnet/advanced-module-reading.md @@ -0,0 +1,170 @@ +# 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 +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. + +``` csharp +var parameters = new ModuleReaderParameters(); +``` + +These parameters can then be passed on to any of the +`ModuleDefinition.FromXXX` methods. + +``` csharp +var module = ModuleDefinition.FromFile(@"C:\Path\To\File.exe", parameters); +``` + +## PE image reading parameters + +.NET modules are stored in a normal PE file. To customize the way +AsmResolver reads the underlying PE image before it is being interpreted +as a .NET image, `ModuleReaderParameters` provides a +`PEReaderParameters` property that can be modified or replaced +completely. + +``` csharp +parameters.PEReaderParameters = new PEReaderParameters +{ + ... +}; +``` + +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; +``` + +Alternatively, this property can also be set through the constructor of +the `ModuleReaderParameters` class directly: + +``` csharp +var parameters = new ModuleReaderParameters(EmptyErrorListener.Instance); +``` + +For more information on customizing the underlying PE image reading +process, see [Advanced PE Image Reading](../peimage/advanced-pe-reading.md). + +## Changing working directory + +Modules often depend on other assemblies. These assemblies often are +placed in the same directory as the original module. However, should +this not be the case, it is possible to change the path of the working +directory of the resolvers. + +``` csharp +parameters.WorkingDirectory = @"C:\Path\To\Different\Folder"; +``` + +Alternatively, this property can also be set through the constructor of +the `ModuleReaderParameters` class directly: + +``` csharp +var parameters = new ModuleReaderParameters(@"C:\Path\To\Different\Folder"); +``` + +## Custom .netmodule resolvers + +For multi-module assemblies, AsmResolver looks into the path stored in +`WorkingDirectory` for files with the .netmodule extension by default. +If it is necessary to change this behaviour, it is possible to provide a +custom implementation of the `INetModuleResolver` interface. + +``` csharp +public class CustomNetModuleResolver : INetModuleResolver +{ + public ModuleDefinition Resolve(string name) + { + // ... + } +} +``` + +To let the reader use this implementation of the `INetModuleResolver`, +set the `NetModuleResolver` property of the reader parameters. + +``` csharp +parameters.NetModuleResolver = new CustomNetModuleResolver(); +``` + +## Custom method body readers + +Some .NET obfuscators store the implementation of method definitions in +an encrypted form, use native method bodies, or use a custom format that +is interpreted at runtime by the means of JIT hooking. To change the way +of how method bodies are being read, it is possible to provide a custom +implementation of the `IMethodBodyReader` interface, or extend the +default implementation. + +Below an example of how to add support for reading simple x86 method +bodies: + +``` csharp +public class CustomMethodBodyReader : DefaultMethodBodyReader +{ + public override MethodBody ReadMethodBody( + ModuleReaderContext context, + MethodDefinition owner, + in MethodDefinitionRow row) + { + if (owner.IsNative && row.Body.CanRead) + { + // Create raw binary reader if method is native. + var reader = row.Body.CreateReader(); + + // Read until the first occurrence of a ret instruction (opcode 0xC3). + // Note: This is for demonstration purposes only, and is by no means + // a very accurate heuristic for finding the boundaries of native + // method bodies. + + var code = reader.ReadBytesUntil(0xC3); + + // Create native method body. + return new NativeMethodBody(owner, code); + } + + // Off-load to default implementation. + return base.ReadMethodBody(context, owner, row); + } +} +``` + +To let the reader use this implementation of the `IMethodBodyReader`, +set the `MethodBodyReader` property of the reader parameters. + +``` csharp +parameters.MethodBodyReader = new CustomMethodBodyReader(); +``` + +## Custom Field RVA reading + +By default, the field RVA data storing the initial binary value of a +field is interpreted as raw byte blobs, and are turned into instances of +the `DataSegment` class. To adjust this behaviour, it is possible to +provide a custom implementation of the `IFieldRvaDataReader` interface. + +``` csharp +public class CustomFieldRvaDataReader : FieldRvaDataReader +{ + public override ISegment ResolveFieldData( + IErrorListener listener, + Platform platform, + IDotNetDirectory directory, + in FieldRvaRow fieldRvaRow) + { + // ... + } +} +``` + +To let the reader use this implementation of the `IFieldRvaDataReader`, +set the `FieldRvaDataReader` property of the reader parameters. + +``` csharp +parameters.FieldRvaDataReader = new CustomFieldRvaDataReader(); +``` diff --git a/docs/articles/dotnet/advanced-pe-image-building.md b/docs/articles/dotnet/advanced-pe-image-building.md new file mode 100644 index 000000000..72b41a5c9 --- /dev/null +++ b/docs/articles/dotnet/advanced-pe-image-building.md @@ -0,0 +1,315 @@ +# Advanced PE Image Building + +The easiest way to write a .NET module to the disk is by using the +`Write` method: + +``` csharp +module.Write(@"C:\Path\To\Output\Binary.exe"); +``` + +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 that takes +instances of `IPEImageBuilder` instead: + +``` csharp +var imageBuilder = new ManagedPEImageBuilder(); + +/* Configuration of imageBuilder here... */ + +module.Write(@"C:\Path\To\Output\Binary.exe", imageBuilder); +``` + +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: + +``` 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. + +``` csharp +var imageBuilder = new ManagedPEImageBuilder(); + +/* Configuration of imageBuilder here... */ + +// Construct image. +var result = imageBuilder.CreateImage(module); + +/* Inspect build result ... */ + +// Obtain constructed PE image. +var image = result.ConstructedImage; + +/* Post processing of image happens here... */ + +// Write image to the disk. +var fileBuilder = new ManagedPEFileBuilder(); +var file = fileBuilder.CreateFile(image); +file.Write(@"C:\Path\To\Output\Binary.exe"); +``` + +This article explores various features about the `ManagedPEImageBuilder` +class. + +## Token mappings + +Upon constructing a new PE image for a module, members defined in the +module might be re-ordered. This can make post-processing of the PE +image difficult, as metadata members cannot be looked up by their +original metadata token anymore. The `PEImageBuildResult` object +returned by `CreateImage` defines a property called `TokenMapping`. This +object maps all members that were included in the construction of the PE +image to the newly assigned metadata tokens, allowing for new metadata +rows to be looked up easily and efficiently. + +``` csharp +var mainMethod = module.ManagedEntrypointMethod; + +// Build PE image. +var result = imageBuilder.CreateImage(module); + +// Look up the new metadata row assigned to the main method. +var newToken = result.TokenMapping[mainMethod]; +var mainMethodRow = result.ConstructedImage.DotNetDirectory.Metadata + .GetStream() + .GetTable() + .GetByRid(newToken.Rid); +``` + +## Preserving raw metadata structure + +Some .NET modules are carefully crafted and rely on the raw structure of +all metadata streams. These kinds of modules often rely on one of the +following: + +- RIDs of rows within a metadata table. +- Indices of blobs within the `#Blob`, `#Strings`, `#US` or `#GUID` + heaps. +- Unknown or unconventional metadata streams and their order. + +The default PE image builder for .NET modules (`ManagedPEImageBuilder`) +defines a property called `DotNetDirectoryFactory`, which contains the +object responsible for constructing the .NET data directory, can be +configured to preserve as much of this structure as possible. With the +help of the `MetadataBuilderFlags` enum, it is possible to indicate +which structures of the metadata directory need to preserved. The +following table provides an overview of all preservation metadata +builder flags that can be used and combined: + +| Flag | Description | +|-----------------------------|-----------------------------------------------------------------| +|`PreserveXXXIndices` | Preserves all row indices of the original `XXX` metadata table. | +|`PreserveTableIndices` | Preserves all row indices from all original metadata tables. | +|`PreserveBlobIndices` | Preserves all blob indices in the `#Blob` stream. | +|`PreserveGuidIndices` | Preserves all GUID indices in the `#GUID` stream. | +|`PreserveStringIndices` | Preserves all string indices in the `#Strings` stream. | +|`PreserveUserStringIndices` | Preserves all user-string indices in the `#US` stream. | +|`PreserveUnknownStreams` | Preserves any of the unknown / unconventional metadata streams. | +|`PreserveStreamOrder` | Preserves the original order of all metadata streams. | +|`PreserveAll` | Preserves as much of the original metadata as possible. | + +Below is an example on how to configure the image builder to preserve +blob data and all metadata tokens to type references: + +``` csharp +var factory = new DotNetDirectoryFactory(); +factory.MetadataBuilderFlags = MetadataBuilderFlags.PreserveBlobIndices + | MetadataBuilderFlags.PreserveTypeReferenceIndices; +imageBuilder.DotNetDirectoryFactory = factory; +``` + +> [!WARNING] +> Preserving heap indices copies over the original contents of the heaps +> to the new PE image \"as-is\". While AsmResolver tries to reuse blobs +> defined in the original heaps as much as possible, this is often not +> possible without also preserving RIDs in the tables stream. This might +> result in a significant increase in file size. + +> [!NOTE] +> Preserving RIDs within metadata tables might require AsmResolver to +> inject placeholder rows in existing metadata tables that are solely +> there to fill up space between existing rows. + +> [!WARNING] +> Preserving RIDs within metadata tables might require AsmResolver to make +> use of the Edit-And-Continue metadata tables (such as the pointer +> tables). The resulting tables stream could therefore be renamed from +> `#~` to `#-`, and the file size might increase. + + +## String folding in #Strings stream + +Named metadata members (such as types, methods and fields) are assigned +a name by referencing a string in the `#Strings` stream by its starting +offset. When a metadata member has a name that is a suffix of another +member\'s name, then it is possible to only store the longer name in the +`#Strings` stream, and let the member with the shorter name use an +offset within the middle of this longer name. For example, consider two +members with the names `ABCDEFG` and `DEFG`. If `ABCDEFG` is stored at +offset `1`, then the name `DEFG` is implicitly defined at offset +`1 + 3 = 4`, and can thus be referenced without appending `DEFG` to the +stream a second time. + +By default, the PE image builder will fold strings in the `#Strings` +stream as described in the above. However, for some input binaries, this +might make the building process take a significant amount of time. +Therefore, to disable this folding of strings, specify the +`NoStringsStreamOptimization` flag in your `DotNetDirectoryFactory`: + +``` csharp +factory.MetadataBuilderFlags |= MetadataBuilderFlags.NoStringsStreamOptimization; +``` + +> [!WARNING] +> Some obfuscated binaries might include lots of members that have very +> long but similar names. For these types of binaries, disabling this +> optimization can result in a significantly larger output file size. + +> [!NOTE] +> When `PreserveStringIndices` is set and string folding is enabled +> (`NoStringsStreamOptimization` is unset), the PE image builder will not +> fold strings from the original `#Strings` stream into each other. +> However, it will still try to reuse these original strings as much as +> possible. + +## Preserving maximum stack depth + +CIL method bodies work with a stack, and the stack has a pre-defined +size. This pre-defined size is defined by the `MaxStack` property of the +`CilMethodBody` class. By default, AsmResolver automatically calculates +the maximum stack depth of a method body upon writing the module to the +disk. However, this is not always desirable. + +To override this behaviour, set `ComputeMaxStackOnBuild` to `false` on +all method bodies to exclude in the maximum stack depth calculation. + +Alternatively, if you want to force the maximum stack depths should be +either preserved or recalculated, it is possible to provide a custom +implemenmtation of the `IMethodBodySerializer`, or configure the +`CilMethodBodySerializer`. + +Below an example on how to preserve maximum stack depths for all methods +in the assembly: + +``` csharp +DotNetDirectoryFactory factory = ...; +factory.MethodBodySerializer = new CilMethodBodySerializer +{ + ComputeMaxStackOnBuildOverride = false +} +``` + +> [!WARNING] +> Disabling max stack computation may have unexpected side-effects (such +> as rendering certain CIL method bodies invalid). + +## Strong name signing + +Assemblies can be signed with a strong-name signature. Open a strong +name private key from a file: + +``` csharp +var snk = StrongNamePrivateKey.FromFile(@"C:\Path\To\keyfile.snk"); +``` + +Prepare the image builder to delay-sign the PE image: + +``` csharp +DotNetDirectoryFactory factory = ...; +factory.StrongNamePrivateKey = snk; +``` + +After writing the module to an output stream, use the `StrongNameSigner` +class to sign the image. + +``` csharp +using Stream outputStream = ... +module.Write(outputStream, factory); + +var signer = new StrongNameSigner(snk); +signer.SignImage(outputStream, module.Assembly.HashAlgorithm); +``` + +## 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 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: + +``` csharp +// Set up a diagnostic bag as an error listener. +var diagnosticBag = new DiagnosticBag(); +imageBuilder.ErrorListener = diagnosticBag; + +// Build image. +var result = imageBuilder.CreateImage(module); + +// Print all errors. +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 simply build +the PE image ignoring all diagnostic errors, it is also possible to pass +in `EmptyErrorListener.Instance` instead: + +``` csharp +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 +> [Advanced Module Reading](advanced-module-reading.md) 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: + +``` csharp +if (!result.HasFailed) +{ + var fileBuilder = new ManagedPEFileBuilder(); + var file = fileBuilder.CreateFile(result.ConstructedImage); + file.Write("output.exe"); +} +``` diff --git a/docs/articles/dotnet/basics.md b/docs/articles/dotnet/basics.md new file mode 100644 index 000000000..a1e79b54a --- /dev/null +++ b/docs/articles/dotnet/basics.md @@ -0,0 +1,157 @@ +# Basic I/O + +Every .NET image interaction is done through classes defined by the +`AsmResolver.DotNet` namespace: + +``` csharp +using AsmResolver.DotNet; +``` + +## Creating a new .NET module + +Creating a new image can be done by instantiating a `ModuleDefinition` +class: + +``` csharp +var module = new ModuleDefinition("MyModule.exe"); +``` + +The above will create a module that references mscorlib.dll 4.0.0.0 +(.NET Framework 4.0). If another version of the Common Object Runtime +Library is desired, we can use one of the overloads of the constructor, +and use a custom `AssemblyReference`, or one of the pre-defined assembly +references in the `KnownCorLibs` class to target another version of the +library. + +``` csharp +var module = new ModuleDefinition("MyModule.exe", KnownCorLibs.SystemRuntime_v4_2_2_0); +``` + +## Opening a .NET module + +Opening a .NET module can be done through one of the `FromXXX` methods +from the `ModuleDefinition` class: + +``` csharp +byte[] raw = ... +var module = ModuleDefinition.FromBytes(raw); +``` + +``` csharp +var module = ModuleDefinition.FromFile(@"C:\myfile.exe"); +``` + +``` csharp +PEFile peFile = ... +var module = ModuleDefinition.FromFile(peFile); +``` + +``` csharp +BinaryStreamReader reader = ... +var module = ModuleDefinition.FromReader(reader); +``` + +``` csharp +IPEImage peImage = ... +var module = ModuleDefinition.FromImage(peImage); +``` + +If you want to read large files (+100MB), consider using memory mapped +I/O instead: + +``` csharp +using var service = new MemoryMappedFileService(); +var module = ModuleDefinition.FromFile(service.OpenFile(@"C:\myfile.exe")); +``` + +On Windows, if a module is loaded and mapped in memory (e.g. as a +dependency defined in Metadata or by the means of `System.Reflection`), +it is possible to load the module from memory by using `FromModule`, or +by transforming the module into a `HINSTANCE` and then providing it to +the `FromModuleBaseAddress` method: + +``` csharp +Module module = ...; +var module = ModuleDefinition.FromModule(module); +``` + +``` csharp +Module module = ...; +IntPtr hInstance = Marshal.GetHINSTANCE(module); +var module = ModuleDefinition.FromModuleBaseAddress(hInstance); +``` + +## Writing a .NET module + +Writing a .NET module can be done through one of the `Write` method +overloads. + +``` csharp +module.Write(@"C:\myfile.patched.exe"); +``` + +``` csharp +Stream stream = ...; +module.Write(stream); +``` + +For more advanced options to write .NET modules, see +[Advanced PE Image Building](advanced-pe-image-building.md). + +## Creating a new .NET assembly + +AsmResolver also supports creating entire (multi-module) .NET assemblies +instead. + +``` csharp +var assembly = new AssemblyDefinition("MyAssembly", new Version(1, 0, 0, 0)); +``` + +## Opening a .NET assembly + +Opening (multi-module) .NET assemblies can be done in a very similar +fashion as reading a single module: + +``` csharp +byte[] raw = ... +var assembly = AssemblyDefinition.FromBytes(raw); +``` + +``` csharp +var assembly = AssemblyDefinition.FromFile(@"C:\myfile.exe"); +``` + +``` csharp +IPEFile peFile = ... +var assembly = AssemblyDefinition.FromFile(peFile); +``` + +``` csharp +BinaryStreamReader reader = ... +var assembly = AssemblyDefinition.FromReader(reader); +``` + +``` csharp +IPEImage peImage = ... +var assembly = AssemblyDefinition.FromImage(peImage); +``` + +Similar to reading module definitions, if you want to read large files +(+100MB), consider using memory mapped I/O instead: + +``` csharp +using var service = new MemoryMappedFileService(); +var assembly = AssemblyDefinition.FromFile(service.OpenFile(@"C:\myfile.exe")); +``` + +## Writing a .NET assembly + +Writing a .NET assembly can be done through one of the `Write` method +overloads. + +``` csharp +assembly.Write(@"C:\myfile.patched.exe"); +``` + +For more advanced options to write .NET assemblies, see +[Advanced PE Image Building](advanced-pe-image-building.md). diff --git a/docs/articles/dotnet/bundles.md b/docs/articles/dotnet/bundles.md new file mode 100644 index 000000000..c4243cc2b --- /dev/null +++ b/docs/articles/dotnet/bundles.md @@ -0,0 +1,237 @@ +# AppHost / SingleFileHost Bundles + +Since the release of .NET Core 3.1, it is possible to deploy .NET +assemblies as a single binary. These files are executables that do not +contain a traditional .NET metadata header, and run natively on the +underlying operating system via a platform-specific application host +bootstrapper. + +AsmResolver supports extracting the embedded files from these types of +binaries. Additionally, given the original file or an application host +template provided by the .NET SDK, AsmResolver also supports +constructing new bundles as well. All relevant code is found in the +following namespace: + +``` csharp +using AsmResolver.DotNet.Bundles; +``` + +## Creating Bundles + +.NET bundles are represented using the `BundleManifest` class. Creating +new bundles can be done using any of the constructors: + +``` csharp +var manifest = new BundleManifest(majorVersionNumber: 6); +``` + +The major version number refers to the file format that should be used +when saving the manifest. Below an overview of the values that are +recognized by the CLR: + + --------------------------------------------------- + .NET Version Number Bundle File Format Version + ---------------------- ---------------------------- + .NET Core 3.1 1 + + .NET 5.0 2 + + .NET 6.0 6 + --------------------------------------------------- + +To create a new bundle with a specific bundle identifier, use the +overloaded constructor + +``` csharp +var manifest = new BundleManifest(6, "MyBundleID"); +``` + +It is also possible to change the version number as well as the bundle +ID later, since these values are exposed as mutable properties +`MajorVersion` and `BundleID` + +``` csharp +manifest.MajorVersion = 6; +manifest.BundleID = manifest.GenerateDeterministicBundleID(); +``` + +> [!NOTE] +> If `BundleID` is left unset (`null`), it will be automatically assigned +> a new one using `GenerateDeterministicBundleID` upon writing. + +## Reading Bundles + +Reading and extracting existing bundle manifests from an executable can +be done by using one of the `FromXXX` methods: + +``` csharp +var manifest = BundleManifest.FromFile(@"C:\Path\To\Executable.exe"); +``` + +``` csharp +byte[] contents = ... +var manifest = BundleManifest.FromBytes(contents); +``` + +``` csharp +IDataSource contents = ... +var manifest = BundleManifest.FromDataSource(contents); +``` + +Similar to the official .NET bundler and extractor, the methods above +locate the bundle in the file by looking for a specific signature first. +However, official implementations of the application hosting program +itself actually do not verify or use this signature in any shape or +form. This means that a third party can replace or remove this +signature, or write their own implementation of an application host that +does not adhere to this standard, and thus throw off static analysis of +the file. + +AsmResolver does not provide built-in alternative heuristics for finding +the right start address of the bundle header. However, it is possible to +implement one yourself and provide the resulting start address in one of +the overloads of the `FromXXX` methods: + +``` csharp +byte[] contents = ... +ulong bundleAddress = ... +var manifest = BundleManifest.FromBytes(contents, bundleAddress); +``` + +``` csharp +IDataSource contents = ... +ulong bundleAddress = ... +var manifest = BundleManifest.FromDataSource(contents, bundleAddress); +``` + +## Writing Bundles + +Constructing new bundled executable files requires a template file that +AsmResolver can base the final output on. This is similar how .NET +compilers themselves do this as well. By default, the .NET SDK installs +template binaries in one of the following directories: + +- `/sdk//AppHostTemplate` +- `/packs/Microsoft.NETCore.App.Host.//runtimes//native` + +Using this template file, it is then possible to write a new bundled +executable file using `WriteUsingTemplate` and the +`BundlerParameters::FromTemplate` method: + +``` csharp +BundleManifest manifest = ... +manifest.WriteUsingTemplate( + @"C:\Path\To\Output\File.exe", + BundlerParameters.FromTemplate( + appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", + appBinaryPath: @"HelloWorld.dll")); +``` + +Typically on Windows, use an `apphost.exe` template if you want to +construct a native binary that is framework dependent, and +`singlefilehost.exe` for a fully self-contained binary. On Linux, use +the `apphost` and `singlefilehost` ELF equivalents. + +For bundle executable files targeting Windows, it may be required to +copy over some values from the original PE file into the final bundle +executable file. Usually these values include fields from the PE headers +(such as the executable\'s sub-system target) and Win32 resources (such +as application icons and version information). AsmResolver can +automatically update these headers by specifying a source image to pull +this data from in the `BundlerParameters`: + +``` csharp +BundleManifest manifest = ... +manifest.WriteUsingTemplate( + @"C:\Path\To\Output\File.exe", + BundlerParameters.FromTemplate( + appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", + appBinaryPath: @"HelloWorld.dll", + imagePathToCopyHeadersFrom: @"C:\Path\To\Original\HelloWorld.exe")); +``` + +If you do not have access to a template file (e.g., if the SDK is not +installed) but have another existing PE file that was packaged in a +similar fashion, it is then possible to use this file as a template +instead by extracting the bundler parameters using the +`BundlerParameters::FromExistingBundle` method. This is in particularly +useful when trying to patch existing AppHost bundles: + +``` csharp +string inputPath = @"C:\Path\To\Bundled\HelloWorld.exe"; +string outputPath = Path.ChangeExtension(inputPath, ".patched.exe"); + +// Read manifest. +var manifest = BundleManifest.FromFile(inputPath); + +/* ... Make changes to manifest and its files ... */ + +// Repackage bundle using existing bundle as template. +manifest.WriteUsingTemplate( + outputPath, + BundlerParameters.FromExistingBundle( + originalFile: inputPath, + appBinaryPath: mainFile.RelativePath)); +``` + +> [!WARNING] +> The `BundlerParameters.FromExistingBundle` method applies heuristics on +> the input file to determine the parameters for patching the input file. +> As heuristics are not perfect, this is not guaranteed to always work. + +`BundleManifest` and `BundlerParameters` also define overloads of the +`WriteUsingTemplate` and `FromTemplate` / `FromExistingBundle` +respectively, taking `byte[]`, `IDataSource` or `IPEImage` instances +instead of file paths. + +## Managing Files + +Files in a bundle are represented using the `BundleFile` class, and are +exposed by the `BundleManifest.Files` property. Both the class as well +as the list itself is fully mutable, and thus can be used to add, remove +or modify files in the bundle. + +Creating a new file can be done using the constructors: + +``` csharp +var newFile = new BundleFile( + relativePath: "HelloWorld.dll", + type: BundleFileType.Assembly, + contents: System.IO.File.ReadAllBytes(@"C:\Binaries\HelloWorld.dll")); + +manifest.Files.Add(newFile); +``` + +It is also possible to iterate over all files and inspect their contents +using `GetData`: + +``` csharp +foreach (var file in manifest.Files) +{ + string path = file.RelativePath; + byte[] contents = file.GetData(); + + Console.WriteLine($"Extracting {path}..."); + System.IO.File.WriteAllBytes(path, contents); +} +``` + +Changing the contents of an existing file can be done using the +`Contents` property. + +``` csharp +BundleFile file = ... +file.Contents = new DataSegment(new byte[] { 1, 2, 3, 4 }); +``` + +If the bundle manifest is put into a single-file host template (e.g. +`singlefilehost.exe`), then files can also be compressed or +decompressed: + +``` csharp +file.Compress(); +// file.Contents now contains the compressed version of the data and file.IsCompressed = true + +file.Decompress(); +// file.Contents now contains the decompressed version of the data and file.IsCompressed = false +``` diff --git a/docs/articles/dotnet/cloning.md b/docs/articles/dotnet/cloning.md new file mode 100644 index 000000000..f197539a9 --- /dev/null +++ b/docs/articles/dotnet/cloning.md @@ -0,0 +1,304 @@ +# Member Cloning + +Processing a .NET module often involves injecting additional code. Even +though all models representing .NET metadata and CIL code are mutable, +it might be very time-consuming and error-prone to manually import and +inject metadata members and/or CIL code into the target module. + +To help developers in injecting existing code into a module, +`AsmResolver.DotNet` comes with a feature that involves cloning metadata +members from one module and copying it over to another. All relevant +classes are in the `AsmResolver.DotNet.Cloning` namespace: + +``` csharp +using AsmResolver.DotNet.Cloning; +``` + +## The MemberCloner class + +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:`. + +``` csharp +ModuleDefinition 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 + +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`: + +``` csharp +public class Rectangle +{ + public Rectangle(Vector2 location, Vector2 size) + { + Location = location; + Size = size; + } + + public Vector2 Location { get; set; } + public Vector2 Size { get; set; } + + public Vector2 GetCenter() => new Vector2(Location.X + Size.X / 2, Location.Y + Size.Y / 2); +} + +public class Vector2 +{ + public Vector2(int x, int y) + { + X = x; + Y = y; + } + + public int X { get; set; } + public int Y { get; set; } +} +``` + +The first step in cloning involves loading the source module, and +finding the type definitions that correspond to these classes: + +``` csharp +var sourceModule = ModuleDefinition.FromFile(...); +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. + +``` csharp +var sourceModule = ModuleDefinition.FromFile(typeof(Rectangle).Assembly.Location); +var rectangleType = (TypeDefinition) sourceModule.LookupMember(typeof(Rectangle).MetadataToken); +var vectorType = (TypeDefinition) sourceModule.LookupMember(typeof(Vector2).MetadataToken); +``` + +We can then use `MemberCloner.Include` to include the types in the +cloning procedure: + +``` csharp +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. This value is `true` by default and can +also be omitted. + +``` 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. + +``` csharp +cloner + .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 + +When all members are included, it is possible to call +`MemberCloner.Clone` to clone them all in one go. + +``` 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. + +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. + +## 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: + +``` 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: + +``` 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 [Common Caveats using the Importer](/articles/dotnet/importing.html#common-caveats-using-the-importer) +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 is an example that appends the string `_Cloned` to the name for +every cloned type. + +``` 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: + +``` 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. + +``` 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. + +``` csharp +var cloner = new MemberCloner(destinationModule, (original, cloned) => { + /* ... Do post processing here ... */ +}); +``` + +## Injecting the cloned members + +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. +- `ClonedTopLevelTypes`: A subset of `ClonedMembers`, containing all + cloned top-level types. + +Original members can be mapped to their cloned counterpart, using the +`GetClonedMember` method: + +``` csharp +var clonedRectangleType = result.GetClonedMember(rectangleType); +``` + +Alternatively, we can get all cloned top-level types. + +``` csharp +var clonedTypes = result.ClonedTopLevelTypes; +``` + +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: + +``` csharp +foreach (var clonedType in clonedTypes) + 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: + +``` csharp +new MemberCloner(destinationModule, new InjectTypeClonerListener(destinationModule)) + .Include(rectangleType) + .Include(vectorType) + .Clone(); + +// `destinationModule` now contains copies of `rectangleType` and `vectorType`. +``` diff --git a/docs/articles/dotnet/dynamic-methods.md b/docs/articles/dotnet/dynamic-methods.md new file mode 100644 index 000000000..6dc0a07bc --- /dev/null +++ b/docs/articles/dotnet/dynamic-methods.md @@ -0,0 +1,72 @@ +# 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: + +``` 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`: + +``` 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: + +``` 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: + +``` 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 [Obtaining methods and fields](/articles/dotnet/member-tree.html#obtaining-methods-and-fields) +and [CIL Method Bodies](managed-method-bodies.md) for more +information on how to use `MethodDefinition` objects. diff --git a/docs/articles/dotnet/importing.md b/docs/articles/dotnet/importing.md new file mode 100644 index 000000000..b446d9869 --- /dev/null +++ b/docs/articles/dotnet/importing.md @@ -0,0 +1,209 @@ +# Reference Importing + +.NET modules use entries in the TypeRef or MemberRef tables to reference +types or members from external assemblies. Importing references into the +current module, therefore, form a key role when creating new- or +modifying existing .NET modules. When a member is not imported into the +current module, a `MemberNotImportedException` will be thrown when you +are trying to create a PE image or write the module to the disk. + +AsmResolver provides the `ReferenceImporter` class that does most of the +heavy lifting. Obtaining an instance of `ReferenceImporter` can be done +in two ways. + +Either instantiate one yourself: + +``` csharp +ModuleDefinition module = ... +var importer = new ReferenceImporter(module); +``` + +Or obtain the default instance that comes with every `ModuleDefinition` +object. This avoids allocating new reference importers every time. + +``` csharp +ModuleDefinition module = ... +var importer = module.DefaultImporter; +``` + +The example snippets that will follow in this article assume that there +is such a `ReferenceImporter` object instantiated using either of these +two methods, and is stored in an `importer` variable. + +## Importing existing members + +Metadata members from external modules can be imported using the +`ReferenceImporter` class using one of the following members: + +|Member type to import |Method to use | Result type | +|----------------------|----------------| ---------------------| +|`IResolutionScope` |`ImportScope` | `IResolutionScope` | +|`AssemblyReference` |`ImportScope` | `IResolutionScope` | +|`AssemblyDefinition` |`ImportScope` | `IResolutionScope` | +|`ModuleReference` |`ImportScope` | `IResolutionScope` | +|`ITypeDefOrRef` |`ImportType` | `ITypeDefOrRef` | +|`TypeDefinition` |`ImportType` | `ITypeDefOrRef` | +|`TypeReference` |`ImportType` | `ITypeDefOrRef` | +|`TypeSpecification` |`ImportType` | `ITypeDefOrRef` | +|`IMethodDefOrRef` |`ImportMethod` | `IMethodDefOrRef` | +|`MethodDefinition` |`ImportMethod` | `IMethodDefOrRef` | +|`MethodSpecification` |`ImportMethod` | `IMethodDefOrRef` | +|`IFieldDescriptor` |`ImportField` | `IFieldDescriptor` | +|`FieldDefinition` |`ImportField` | `IFieldDescriptor` | + +Below an example of how to import a type definition called `SomeType`: + +``` csharp +ModuleDefinition externalModule = ModuleDefinition.FromFile(...); +TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType"); + +ITypeDefOrRef importedType = importer.ImportType(typeToImport); +``` + +These types also implement the `IImportable` interface. This means you +can also use the `member.ImportWith` method instead: + +``` csharp +ModuleDefinition externalModule = ModuleDefinition.FromFile(...); +TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType"); + +ITypeDefOrRef importedType = typeToImport.ImportWith(importer); +``` + +## Importing existing type signatures + +Type signatures can also be imported using the `ReferenceImporter` +class, but these should be imported using the `ImportTypeSignature` +method instead. + +> [!NOTE] +> If a corlib type signature is imported, the appropriate type from the +> `CorLibTypeFactory` of the target module will be selected, regardless of +> whether CorLib versions are compatible with each other. + + +## Importing using System.Reflection + +Types and members can also be imported by passing on an instance of +various `System.Reflection` classes. + +|Member type to import |Method to use |Result type | +|----------------------|---------------------|---------------------| +|`Type` |`ImportType` |`ITypeDefOrRef` | +|`Type` |`ImportTypeSignature`|`TypeSignature` | +|`MethodBase` |`ImportMethod` |`IMethodDefOrRef` | +|`MethodInfo` |`ImportMethod` |`IMethodDefOrRef` | +|`ConstructorInfo` |`ImportMethod` |`IMethodDefOrRef` | +|`FieldInfo` |`ImportScope` |`MemberReference` | + +There is limited support for importing complex types. Types that can be +imported through reflection include: + +- Pointer types. +- By-reference types. +- Array types (If an array contains only one dimension, a + `SzArrayTypeSignature` is returned. Otherwise a `ArrayTypeSignature` + is created). +- Generic parameters. +- Generic type instantiations. + +Instantiations of generic methods are also supported. + +## Creating new references + +Member references can also be created and imported without having direct +access to its member definition or `System.Reflection` instance. It is +possible to create new instances of `TypeReference` and +`MemberReference` using the constructors, but the preferred way is to +use the factory methods that allow for a more fluent syntax. Below is an +example of how to create a fully imported reference to +`void System.Console.WriteLine(string)`: + +``` csharp +var factory = module.CorLibTypeFactory; +var importedMethod = factory.CorLibScope + .CreateTypeReference("System", "Console") + .CreateMemberReference("WriteLine", MethodSignature.CreateStatic( + factory.Void, factory.String)) + .ImportWith(importer); + +// importedMethod now references "void System.Console.WriteLine(string)" +``` + +Generic type instantiations can also be created using +`MakeGenericInstanceType`: + +``` csharp +ModuleDefinition module = ... + +var factory = module.CorLibTypeFactory; +var importedMethod = factory.CorLibScope + .CreateTypeReference("System.Collections.Generic", "List`1") + .MakeGenericInstanceType(factory.Int32) + .ToTypeDefOrRef() + .CreateMemberReference("Add", MethodSignature.CreateInstance( + factory.Void, + new GenericParameterSignature(GenericParameterType.Type, 0))) + .ImportWith(importer); + +// importedMethod now references "System.Collections.Generic.List`1.Add(!0)" +``` + +Similarly, generic method instantiations can be constructed using +`MakeGenericInstanceMethod`: + +``` csharp +ModuleDefinition module = ... + +var factory = module.CorLibTypeFactory; +var importedMethod = factory.CorLibScope + .CreateTypeReference("System", "Array") + .CreateMemberReference("Empty", MethodSignature.CreateStatic( + new GenericParameterSignature(GenericParameterType.Method, 0).MakeSzArrayType(), 1)) + .MakeGenericInstanceMethod(factory.String) + .ImportWith(importer); + +// importedMethod now references "!0[] System.Array.Empty()" +``` + +## Common Caveats using the Importer + +### Caching and reuse of instances + +The default implementation of `ReferenceImporter` does not maintain a +cache. Each call to any of the import methods will result in a new +instance of the imported member. The exception to this rule is when the +member passed onto the importer is defined in the module the importer is +targeting itself, or was already a reference imported by an importer +into the target module. In both of these cases, the same instance of +this member definition or reference will be returned instead. + +### Importing cross-framework versions + +The `ReferenceImporter` does not support importing across different +versions of the target framework. Members are being imported as-is, and +are not automatically adjusted to conform with other versions of a +library. + +As a result, trying to import from for example a library part of the +.NET Framework into a module targeting .NET Core or vice versa has a +high chance of producing an invalid .NET binary that cannot be executed +by the runtime. For example, attempting to import a reference to +`[System.Runtime] System.DateTime` into a module targeting .NET +Framework will result in a new reference targeting a .NET Core library +(`System.Runtime`) as opposed to the appropriate .NET Framework library +(`mscorlib`). + +This is a common mistake when trying to import using metadata provided +by `System.Reflection`. For example, if the host application that uses +AsmResolver targets .NET Core but the input file is targeting .NET +Framework, then you will run in the exact issue described in the above. + +``` csharp +var reference = importer.ImportType(typeof(DateTime)); + +// `reference` will target `[mscorlib] System.DateTime` when running on .NET Framework, and `[System.Runtime] System.DateTime` when running on .NET Core. +``` + +Therefore, always make sure you are importing from a .NET module that is +compatible with the target .NET module. diff --git a/docs/articles/dotnet/index.md b/docs/articles/dotnet/index.md new file mode 100644 index 000000000..b509b82c6 --- /dev/null +++ b/docs/articles/dotnet/index.md @@ -0,0 +1,22 @@ +# Overview + +The .NET image layer is the third layer of abstraction of the portable +executable (PE) file format. It provides a high-level abstraction of the +.NET metadata stored in a PE image, that is similar to APIs like +`System.Reflection`. Its root objects are `AssemblyDefinition` and +`ModuleDefinition`, and from there it is possible to dissect the .NET +assembly hierarchically. + +In short, this means the following: + +- Assemblies define modules, +- Modules define types, resources and external references, +- Types define members such as methods, fields, properties and events, +- Methods include method bodies, +- \... and so on. + +The third layer of abstraction is the highest level of abstraction for a +.NET assembly that AsmResolver provides. All objects exposed by this +layer are completely mutable and can be serialized back to a `IPEImage` +from the second layer, to a `PEFile` from the first layer, or to the +disk. diff --git a/docs/articles/dotnet/managed-method-bodies.md b/docs/articles/dotnet/managed-method-bodies.md new file mode 100644 index 000000000..b0771a84f --- /dev/null +++ b/docs/articles/dotnet/managed-method-bodies.md @@ -0,0 +1,345 @@ +# CIL Method Bodies + +The relevant models in this document can be found in the following +namespaces: + +``` csharp +using AsmResolver.PE.DotNet.Cil; // Raw models for the CIL language. +using AsmResolver.DotNet.Code.Cil; // High level and easy to use models related to CIL. +``` + +## The CilMethodBody class + +The `MethodDefinition` class defines a property called `CilMethodBody`, +which exposes the managed implementation of the method, written in the +Common Intermediate Language, or CIL for short. + +Each `CilMethodBody` is assigned to exactly one `MethodDefinition`. Upon +instantiation of such a method body, it is therefore required to specify +the owner of the body: + +``` csharp +MethodDefinition method = ... + +CilMethodBody body = new CilMethodBody(method); +method.CilMethodBody = body; +``` + +The `CilMethodBody` class consists of the following basic building +blocks: + +- `Instructions`: The instructions to be executed. +- `LocalVariables`: The local variables defined by the method body. +- `ExceptionHandlers`: A collection of regions protected by an + exception handler. + +## Basic structure of CIL instructions + +Instructions that are assembled into the method body are automatically +disassembled and put in a mutable collection of `CilInstruction`, +accessible by the `Instructions` property. + +``` csharp +var instructions = body.Instructions; +``` + +The `CilInstruction` class defines three basic properties: + +- `Offset`: The offset of the instruction, relative to the start of + the code stream. +- `OpCode`: The operation the instruction performs. +- `Operand`: The operand of the instruction. + +By default, depending on the value of `OpCode.OperandType`, `Operand` +contains (and always should contain) one of the following: + +|OpCode.OperandType |Type of Operand | +|------------------------------------|----------------------------------------| +|`CilOperandType.InlineNone` |N/A (is always `null`) | +|`CilOperandType.ShortInlineI` |`sbyte` | +|`CilOperandType.InlineI` |`int` | +|`CilOperandType.InlineI8` |`long` | +|`CilOperandType.ShortInlineR` |`float` | +|`CilOperandType.InlineR` |`double` | +|`CilOperandType.InlineString` |`string` or `MetadataToken` | +|`CilOperandType.InlineBrTarget` |`ICilLabel` or `int` | +|`CilOperandType.ShortInlineBrTarget`|`ICilLabel` or `sbyte` | +|`CilOperandType.InlineSwitch` |`IList` | +|`CilOperandType.ShortInlineVar` |`CilLocalVariable` or `byte` | +|`CilOperandType.InlineVar` |`CilLocalVariable` or `ushort` | +|`CilOperandType.ShortInlineArgument`|`Parameter` or `byte` | +|`CilOperandType.InlineArgument` |`Parameter` or `ushort` | +|`CilOperandType.InlineField` |`IFieldDescriptor` or `MetadataToken` | +|`CilOperandType.InlineMethod` |`IMethodDescriptor` or `MetadataToken` | +|`CilOperandType.InlineSig` |`StandAloneSignature` or `MetadataToken`| +|`CilOperandType.InlineTok` |`IMetadataMember` or `MetadataToken` | +|`CilOperandType.InlineType` |`ITypeDefOrRef` or `MetadataToken` | + + +> [!WARNING] +> Providing an incorrect operand type will result in the CIL assembler to +> fail assembling the method body upon writing the module to the disk. + +Creating a new instruction can be done using one of the constructors, +together with the `CilOpCodes` static class: + +``` csharp +body.Instructions.AddRange(new[] +{ + new CilInstruction(CilOpCodes.Ldstr, "Hello, World!"), + new CilInstruction(CilOpCodes.Ret), +}); +``` + +However, the preferred way of adding instructions to add or insert new +instructions is to use one of the `Add` or `Insert` overloads that +directly take an opcode and operand. This is because it avoids an +allocation of an array, and the overloads perform immediate validation +on the created instruction. + +``` csharp +var instructions = body.Instructions; +instructions.Add(CilOpCodes.Ldstr, "Hello, World!"); +instructions.Add(CilOpCodes.Ret); +``` + +## Pushing 32-bit integer constants onto the stack + +In CIL, pushing integer constants onto the stack is done using one of +the `ldc.i4` instruction variants. + +The recommended way to create such an instruction is not to use the +constructor, but instead use the `CilInstruction.CreateLdcI4(int)` +method instead. This automatically selects the smallest possible opcode +possible and sets the operand accordingly: + +``` csharp +CilInstruction push1 = CilInstruction.CreateLdcI4(1); // Returns "ldc.i4.1" macro +CilInstruction pushShort = CilInstruction.CreateLdcI4(123); // Returns "ldc.i4.s 123" macro +CilInstruction pushLarge = CilInstruction.CreateLdcI4(12345678); // Returns "ldc.i4 12345678" +``` + +If we want to get the pushed value, we can use the +`CilInstruction.GetLdcI4Constant()` method. This method works on any of +the `ldc.i4` variants, including all the macro opcodes that do not +explicitly define an operand such as `ldc.i4.1`. + +## Branching Instructions + +Branch instructions are instructions that (might) transfer control to +another part of the method body. To reference the instruction to jump to +(the branch target), `ICilLabel` is used. The easiest way to create such +a label is to use the `CreateLabel()` function on the instruction to +reference: + +``` csharp +CilInstruction targetInstruction = ... +ICilLabel label = targetInstruction.CreateLabel(); + +instructions.Add(CilOpCodes.Br, label); +``` + +Alternatively, when using the `Add` or `Insert` overloads, it is +possible to use the return value of these overloads. + +``` csharp +var instructions = body.Instructions; +var label = new CilInstructionLabel(); + +instructions.Add(CilOpCodes.Br, label); +/* ... */ +label.Instruction = instruction.Add(CilOpCodes.Ret); +``` + +The `switch` operation uses a `IList` instead. + +> [!NOTE] +> When a branching instruction contains a `null` label or a label that +> references an instruction that is not present in the method body, +> AsmResolver will by default report an exception upon serializing the +> code stream. This can be disabled by setting `VerifyLabelsOnBuild` to +> `false`. + +## Finding instructions by offset + +Instructions stored in a method body are indexed not by offset, but by +order of occurrence. If it is required to find an instruction by offset, +it is possible to use the `Instructions.GetByOffset(int)` method, which +performs a binary search (O(log(n))) and is faster than a linear search +(O(n)) such as a for loop or using a construction like +`.First(i => i.Offset == offset)` provided by `System.Linq`. + +For `GetByOffset` to work, it is required that all offsets in the +instruction collection are up to date. Recalculating all offsets within +an instruction collection can be done through +`Instructions.CalculateOffsets()`. + +``` csharp +// Calculate all offsets once ... +body.Instructions.CalculateOffsets(); + +// Look up multiple times. +var instruction1 = body.Instructions.GetByOffset(0x0012); +var instruction2 = body.Instructions.GetByOffset(0x0020); + +// Find the index of an instruction. +int index = body.Instructions.GetIndexByOffset(0x0012); +instruction1 = body.Instructions[index]; +``` + +## Referencing members + +As specified by the table above, operations such as a `call` require a +member as operand. + +It is important that the member referenced in the operand of such an +instruction is imported in the module. This can be done using the +`ReferenceImporter` class. + +Below an example on how to use the `ReferenceImporter` to emit a call to +`Console::WriteLine(string)` using reflection: + +``` csharp +var importer = new ReferenceImporter(targetModule); +var writeLine = importer.ImportMethod(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) } ); + +body.Instructions.Add(new CilInstruction(CilOpCodes.Ldstr, "Hello, world!")); +body.Instructions.Add(new CilInstruction(CilOpCodes.Call, writeLine)); +``` + +More information on the capabilities and limitations of the +`ReferenceImporter` can be found in +[Reference Importing](importing.md). + +## Expanding and optimising macros + +CIL defines a couple of macro operations that do the same as their full +counterpart, but require less space to be encoded. For example, the +`ldc.i4.1` instruction is a macro for `ldc.i4 1`, and requires 1 byte +instead of 5 bytes to do the same thing. + +AsmResolver is able to expand macros to their larger sized counterparts +and back using the `Instructions.ExpandMacros()` and +`Instructions.OptimizeMacros()`. + +``` csharp +var instruction = new CilInstruction(CilOpCodes.Ldc_I4, 1); +body.Instructions.Add(instruction); + +body.Instructions.OptimizeMacros(); + +// instruction is now optimized to "ldc.i4.1". +``` + +``` csharp +var instruction = new CilInstruction(CilOpCodes.Ldc_I4_1); +body.Instructions.Add(instruction); + +body.Instructions.ExpandMacros(); + +// instruction is now expanded to "ldc.i4 1". +``` + +## Pretty printing CIL instructions + +Instructions can be formatted using e.g. an instance of the +`CilInstructionFormatter`: + +``` csharp +var formatter = new CilInstructionFormatter(); +foreach (CilInstruction instruction in body.Instructions) + Console.WriteLine(formatter.FormatInstruction(instruction)); +``` + +## Patching CIL instructions + +Instructions can be added or removed using the `Add`, `Insert`, `Remove` +and `RemoveAt` methods: + +``` csharp +body.Instructions.Add(CilOpCodes.Ldstr, "Hello, world!"); +body.Instructions.Insert(i, CilOpCodes.Ldc_I4, 1234); +body.Instructions.RemoveAt(i); +``` + +\... or by using the indexer to replace existing instructions: + +``` csharp +body.Instructions[i] = new CilInstruction(CilOpCodes.Ret); +``` + +Removing or replacing instructions may not always be favourable. The +original `CilInstruction` object might be used as a reference for a +branch target or exception handler boundary. Removing or replacing these +`CilInstruction` objects would therefore break these kinds of +references, rendering the body invalid. Rather than updating all +references manually, it may therefore be wiser to reuse the +`CilInstruction` object and simply modify the `OpCode` and `Operand` +properties instead: + +``` csharp +body.Instructions[i].OpCode = CilOpCodes.Ldc_I4; +body.Instructions[i].Operand = 1234; +``` + +AsmResolver provides a helper function `ReplaceWith` that shortens the +code into a single line: + +``` csharp +body.Instructions[i].ReplaceWith(CilOpCodes.Ldc_I4, 1234); +``` + +Since it is very common to replace instructions with a +[nop]{.title-ref}, AsmResolver also defines a special `ReplaceWithNop` +helper function: + +``` csharp +body.Instructions[i].ReplaceWithNop(); +``` + +## Exception handlers + +Exception handlers are regions in the method body that are protected +from exceptions. In AsmResolver, they are represented by the +`CilExceptionHandler` class, and define the following properties: + +- `HandlerType`: The type of handler. +- `TryStart`: The label indicating the start of the protected region. +- `TryEnd`: The label indicating the end of the protected region. This + label is exclusive, i.e. it marks the first instruction that is not + included in the region. +- `HandlerStart`: The label indicating the start of the handler + region. +- `HandlerEnd`: The label indicating the end of the handler region. + This label is exclusive, i.e. it marks the first instruction that is + not included in the region. +- `FilterStart`: The label indicating the start of the filter + expression, if available. +- `ExceptionType`: The type of exceptions that are caught by the + handler. + +Depending on the value of `HandlerType`, either `FilterStart` or +`ExceptionType`, or neither has a value. + +> [!NOTE] +> Similar to branch instructions, when an exception handler contains a +> `null` label or a label that references an instruction that is not +> present in the method body, AsmResolver will report an exception upon +> serializing the code stream. This can be disabled by setting +> `VerifyLabelsOnBuild` to `false`. + +## Maximum stack depth + +CIL method bodies work with a stack, and the stack has a pre-defined +size. This pre-defined size is defined by the `MaxStack` property. + +The max stack can be computed by using the `ComputeMaxStack` method. By +default, AsmResolver automatically calculates the maximum stack depth of +a method body upon writing the module to the disk. If you want to +override this behaviour, set `ComputeMaxStackOnBuild` to `false`. + +> [!NOTE] +> If a `StackImbalanceException` is thrown upon writing the module to the +> disk, or upon calling `ComputeMaxStack`, it means that not all execution +> paths in the provided CIL code push or pop the expected amount of +> values. It is a good indication that the provided CIL code is invalid. diff --git a/docs/articles/dotnet/managed-resources.md b/docs/articles/dotnet/managed-resources.md new file mode 100644 index 000000000..607492851 --- /dev/null +++ b/docs/articles/dotnet/managed-resources.md @@ -0,0 +1,266 @@ +# Managed Resources + +.NET modules may define one or more resource files. Similar to Win32 +resources, these are files that contain additional data, such as images, +strings or audio files, that are used by the module at run time. + +## Manifest Resources + +AsmResolver models managed resources using the `ManifestResource` class, +and they are exposed by the `ModuleDefinition.Resources` property. Below +an example snippet that prints the names of all resources in a given +module: + +``` csharp +var module = ModuleDefinition.FromFile(...); +foreach (var resource in module.Resources) + Console.WriteLine(resource.Name); +``` + +A `ManifestResource` can either be embedded in the module itself, or +present in an external assembly or file. When it is embedded, the +contents of the file can be accessed using the `EmbeddedDataSegment` +property. This is a mutable property, so it is also possible to assign +new data to the resource this way. + +``` csharp +ManifestResource resource = ... +if (resource.IsEmbedded) +{ + // Get data segment of the resource. + var oldData = resource.EmbeddedDataSegment; + + // Assign new data to the resource. + var newData = new DataSegment(new byte[] { 1, 2, 3, 4}); + resource.EmbeddedDataSegment = newData; +} +``` + +The `ManifestResource` class also defines a convenience `GetData` +method, for quickly obtaining the data stored in the resource as a +`byte[]`: + +``` csharp +ManifestResource resource = ... +if (resource.IsEmbedded) +{ + byte[] data = resource.GetData(); + // ... +} +``` + +Alternatively, you can use the `TryGetReader` method to immediately +instantiate a `BinaryStreamReader` for the data. This can be useful if +you want to parse the contents of the resource file later. + +``` csharp +ManifestResource resource = ... +if (resource.TryGetReader(out var reader) +{ + // ... +} +``` + +If the resource is not embedded, the `Implementation` property will +indicate in which file the resource can be found, and `Offset` will +indicate where in this file the data starts. + +``` csharp +ManifestResource resource = ... +switch (resource.Implementation) +{ + case FileReference fileRef: + // Resource is stored in another file. + string name = fileRef.Name; + uint offset = resource.Offset; + ... + break; + + case AssemblyReference assemblyRef: + // Resource is stored in another assembly. + var assembly = assemblyRef.Resolve(); + var actualResource = assembly.ManifestModule.Resources.First(r => r.Name == resource.Name); + ... + break; + + case null: + // Resource is embedded. + ... + break +} +``` + +## Resource Sets + +Many .NET applications (mainly Windows Forms apps) make use of manifest +resources to store *resource sets*. These are resources that have the +`.resources` file extension, and combine multiple smaller resources +(often localized strings or images) into one manifest resource file. + +AsmResolver supports parsing and building new resource sets using the +`ResourceSet` class. This class is defined in the +`AsmResolver.DotNet.Resources` namespace: + +``` csharp +using AsmResolver.DotNet.Resources; +``` + +> [!NOTE] +> Adding this `using` statement might introduce a name resolution conflict +> with the (original) `ResourceSet` class defined in `System.Resources`. +> Generally speaking, you will not need both classes at the same time, as +> `ResourceSet` from AsmResolver is meant to replace the one from +> `System.Resources`. However, if you do need to use both classes in the +> same file, make sure you are using the correct one for your use-case. +> This can for example be achieved by specifying the fully qualified name +> (e.g. `System.Resources.ResourceSet`), or by introducing an alias (e.g. +> `using SystemResourceSet = System.Resources.ResourceSet;`) instead. + +### Creating new Resource Sets + +Creating new sets can be done using the constructors of `ResourceSet`. + +``` csharp +var set = new ResourceSet(); +``` + +By default, the parameterless constructor will create a resource set +with a header that references the `System.Resources.ResourceReader` and +`System.Resources.RuntimeResourceSet` types, both from `mscorlib` +version `4.0.0.0`. This can be customized if needed, by using another +constructor overload that takes a `ResourceManagerHeader` instance +instead: + +``` csharp +var set = new ResourceSet(ResourceManagerHeader.Deserializing_v4_0_0_0); +``` + +Alternatively, you can change the header using the +`ResourceSet.ManagerHeader` property: + +``` csharp +var set = new ResourceSet(); +set.ManagerHeader = ResourceManagerHeader.Deserializing_v4_0_0_0; +``` + +### Reading existing Resource Sets + +Reading existing resource sets can be done using the +`ResourceSet.FromReader` method: + +``` csharp +ManifestResource resource = ... +if (resource.TryGetReader(out var reader) +{ + var set = ResourceSet.FromReader(reader); + // ... +} +``` + +By default, AsmResolver will read and deserialize entries in a resource +set. However, to prevent arbitrary code execution, it will not interpret +the data of each entry that is of a non-intrinsic resource type. For +these types of entries, AsmResolver will expose the raw data as a +`byte[]` instead. If you want to change this behavior, you can provide a +custom instance of `IResourceDataSerializer` or extend the default +serializer so that it supports additional resource types. + +``` csharp +public class MyResourceDataSerializer : DefaultResourceDataSerializer +{ + /// + public override object? Deserialize(ref BinaryStreamReader reader, ResourceType type) + { + // ... + } +} + +ManifestResource resource = ... +if (resource.TryGetReader(out var reader) +{ + var set = ResourceSet.FromReader(reader, new MyResourceDataSerializer()); + // ... +} +``` + +### Accessing Resource Set Entries + +The `ResourceSet` class is a mutable list of `ResourceSetEntry`, which +includes the name, the type of the resource and the deserialized data: + +``` csharp +foreach (var entry in set) +{ + Console.WriteLine("Name: " + entry.Name); + Console.WriteLine("Type: " + entry.Type.FullName); + Console.WriteLine("Data: " + entry.Data); +} +``` + +New items can be created using any of the constructors. + +``` csharp +var stringEntry = new ResourceSetEntry("MyString", ResourceTypeCode.String, "Hello, world!"); +set.Add(stringEntry); + +var intEntry = new ResourceSetEntry("MyInt", ResourceTypeCode.Int32, 1234); +set.Add(intEntry); +``` + +AsmResolver also supports reading and adding resource elements that are +of a user-defined type: + +``` csharp +var pointType = new UserDefinedResourceType( + "System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + +var serializedContents = new byte[] +{ + 0x03, 0x06, 0x31, 0x32, 0x2C, 0x20, 0x33, 0x34 // "12, 34" +}; + +var entry = new ResourceSetEntry("MyLocation", type, serializedContents); +set.Add(entry); +``` + +> [!NOTE] +> When using user-defined types, some implementations of the CLR will +> require a special resource reader type (such as +> `System.Resources.Extensions.DeserializingResourceReader`) to be +> referenced in the manager header of the resource set. Therefore, make +> sure you have the right manager header provided in the `ResourceSet` +> that defines such a compatible reader type. + +### Writing Resource Sets + +Serializing resource sets can be done using the `ResourceSet.Write` +method. + +``` csharp +using var stream = new MemoryStream(); +var writer = new BinaryStreamWriter(stream); +set.Write(writer); +``` + +By default, AsmResolver will serialize entries in a resource set using a +default serializer. However, to prevent arbitrary code execution, it +will not attempt to serialize objects that are of a non-intrinsic +resource type. The default serializer expects a `byte[]` for +user-defined resource types. If you want to change this behavior, you +can provide a custom instance of `IResourceDataSerializer` or extend the +default serializer so that it supports additional resource types. + +``` csharp +public class MyResourceDataSerializer : DefaultResourceDataSerializer +{ + /// + public override void Serialize(IBinaryStreamWriter writer, ResourceType type, object? value) + { + // ... + } +} + +using var stream = new MemoryStream(); +var writer = new BinaryStreamWriter(stream); +set.Write(writer, new MyResourceDataSerializer()); +``` diff --git a/docs/articles/dotnet/member-tree.md b/docs/articles/dotnet/member-tree.md new file mode 100644 index 000000000..a91f64271 --- /dev/null +++ b/docs/articles/dotnet/member-tree.md @@ -0,0 +1,123 @@ +# The Member Tree + +## Assemblies and modules + +The root of every .NET assembly is represented by the +`AssemblyDefinition` class. This class exposes basic information such as +name, version and public key token, but also a collection of all modules +that are defined in the assembly. Modules are represented by the +`ModuleDefinition` class. + +Below an example that enumerates all modules defined in an assembly. + +``` csharp +var assembly = AssemblyDefinition.FromFile(...); +foreach (var module in assembly.Modules) + Console.WriteLine(module.Name); +``` + +Most .NET assemblies only have one module. This main module is also +known as the manifest module, and can be accessed directly through the +`AssemblyDefinition.ManifestModule` property. + +## Obtaining types in a module + +Types are represented by the `TypeDefinition` class. To get the types +defined in a module, use the `ModuleDefinition.TopLevelTypes` property. +A top level types is any non-nested type. Nested types are exposed +through the `TypeDefinition.NestedTypes`. Alternatively, to get all +types, including nested types, it is possible to call the +`ModuleDefinition.GetAllTypes` method instead. + +Below is an example program that iterates through all types recursively +and prints them: + +``` csharp +public const int IndentationWidth = 3; + +private static void Main(string[] args) +{ + var module = ModuleDefinition.FromFile(...); + DumpTypes(module.TopLevelTypes); +} + +private static void DumpTypes(IEnumerable types, int indentationLevel = 0) +{ + string indentation = new string(' ', indentationLevel * IndentationWidth); + foreach (var type in types) + { + // 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); + } +} +``` + +## Obtaining methods and fields + +The `TypeDefinition` class exposes collections of methods and fields +that the type defines: + +``` csharp +foreach (var method in type.Methods) + Console.WriteLine("{0} : {1:X8}", method.Name, method.MetadataToken.ToInt32()); +``` + +``` csharp +foreach (var field in type.Fields) + Console.WriteLine("{0} : {1:X8}", field.Name, field.MetadataToken.ToInt32()); +``` + +Methods and fields have a `Signature` property, that contain the return +and parameter types, or the field type respectively. + +``` csharp +MethodDefinition method = ... +Console.WriteLine("Return type: " + method.Signature.ReturnType); +Console.WriteLine("Parameter types: " + string.Join(", ", method.Signature.ParameterTypes)); +``` + +``` csharp +FieldDefinition field = ... +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: + +``` csharp +foreach (var parameter in method.Parameters) + Console.WriteLine($"{parameter.Name} : {parameter.ParameterType}"); +``` + +## Obtaining properties and events + +Obtaining properties and events is similar to obtaining methods and +fields; `TypeDefinition` exposes them in a list as well: + +``` csharp +foreach (var @event in type.Events) + Console.WriteLine("{0} : {1:X8}", @event.Name, @event.MetadataToken.ToInt32()); +``` + +``` csharp +foreach (var property in type.Properties) + Console.WriteLine("{0} : {1:X8}", property.Name, property.MetadataToken.ToInt32()); +``` + +Properties and events have methods associated to them. These are +accessible through the `Semantics` property: + +``` csharp +foreach (MethodSemantics semantic in property.Semantics) +{ + Console.WriteLine("{0} {1} : {2:X8}", semantic.Attributes, semantic.Method.Name, + semantic.MetadataToken.ToInt32()); +} +``` diff --git a/docs/articles/dotnet/token-allocation.md b/docs/articles/dotnet/token-allocation.md new file mode 100644 index 000000000..ac8896d2b --- /dev/null +++ b/docs/articles/dotnet/token-allocation.md @@ -0,0 +1,43 @@ +# Metadata Token Allocation + +A lot of models in a .NET module are assigned a unique metadata token. +This token can be accessed through the `IMetadataMember.MetadataToken` +property. The exception to this rule is newly created metadata members. +These newly created members are assigned the zero token (a token with +RID = 0). Upon building a module, these tokens will be replaced with +their actual tokens. + +## Custom Token Allocation + +Some use-cases of AsmResolver will depend on the knowledge of tokens of +newly created members before serializing the module. Therefore, +AsmResolver provides the `TokenAllocator` class, which allows for +assigning new tokens to members preemptively. If a module is then +written to the disk with the `MetadataFlags.PreserveTableIndices` flags +set (see Advanced PE Image Building for more information on how that is +done), this token will be preserved in the final image. + +The token allocator for a particular module can be accessed through the +`ModuleDefinition.TokenAllocator` property: + +``` csharp +var allocator = module.TokenAllocator; +``` + +Using the allocator, it is possible to assign metadata tokens to newly +created members. This is done using the `AssignNextAvailableToken` +method: + +``` csharp +var field = new FieldDefinition(...); +someType.Fields.Add(field); + +allocator.AssignNextAvailableToken(field); + +// field.MetadataToken is now non-zero. +``` + +> [!NOTE] +> Only members with a zero Metadata Token can be assigned a new metadata +> token. If a metadata member with a non-zero MetadataToken was passed as +> an argument, this method will throw an `ArgumentException`. diff --git a/docs/articles/dotnet/type-memory-layout.md b/docs/articles/dotnet/type-memory-layout.md new file mode 100644 index 000000000..fb5ac1b0a --- /dev/null +++ b/docs/articles/dotnet/type-memory-layout.md @@ -0,0 +1,141 @@ +# Type Memory Layout + +Sometimes it is useful to know details about the memory layout of a type +at runtime. Knowing the memory layout of a type can help in various +processes, including: + +- Getting the size of a type at runtime. +- Calculating field pointer offsets within a type. + +AsmResolver provides an API to statically infer information about the +memory layout of any given blittable type. It supports structures marked +as `SequentialLayout` and `ExplicitLayout`, as well as field alignments +and custom field offsets. + +To get access to the API, you must include the following namespace: + +``` csharp +using AsmResolver.DotNet.Memory; +``` + +## Obtaining the type layout + +To get the memory layout of any `ITypeDescriptor`, use the following +extension method: + +``` csharp +ITypeDescriptor type = ... +TypeMemoryLayout typeLayout = type.GetImpliedMemoryLayout(is32Bit: false); +``` + +> [!NOTE] +> Only value types that are marked with the `SequentialLayout` or +> `ExplicitLayout` structure layout are fully supported. If `AutoLayout` +> was provided, a sequential layout is assumed. This might not be the case +> for all implementations of the CLR. + + +> [!NOTE] +> If the type contains a cyclic dependency (e.g. a field with field type +> equal to its enclosing class), this method will throw an instance of the +> `CyclicStructureException` class. + +## The size of a type + +After the memory layout is inferred, you can query the `Size` property +to obtain the total size in bytes of the type. + +``` csharp +uint size = typeLayout.Size; +``` + +## Getting field offsets + +The `TypeMemoryLayout` provides an indexer property that takes an +instance of `FieldDefinition`, and returns an instance of +`FieldMemoryLayout`. + +``` csharp +FieldDefinition field = ... +FieldMemoryLayout fieldLayout = typeLayout[field]; +``` + +This class contains the offset of the queried field within the type: + +``` csharp +uint offset = fieldLayout.Offset; +``` + +It also provides another instance of `TypeMemoryLayout` to get the +layout of the contents of the field: + +``` csharp +TypeMemoryLayout contentsLayout = fieldLayout.ContentsLayout; +``` + +This can be used to recursively find fields and their offsets. + +> [!NOTE] +> A `TypeMemoryLayout` describing the layout of type `T` might be +> different from a `TypeMemoryLayout` that was associated to a field, even +> if this field has field type `T` as well. This is due to the fact that +> the CLR might layout nested fields differently when a structure defines +> a field with a compound field type. + + +## Getting fields by offset + +It is also possible to turn an offset (relative to the start of the +type) to the field definition that is stored at that offset. This is +done by using the `TryGetFieldAtOffset` method. + +``` csharp +uint offset = ... +if (typeLayout.TryGetFieldAtOffset(offset, out var fieldLayout)) +{ + // There is a field defined at this offset. +} +``` + +Sometimes, offsets within a structure refer to a field within a nested +field. For example, consider the following sample code: + +``` csharp +[StructLayout(LayoutKind.Sequential, Size = 17)] +public struct Struct1 +{ + public int Dummy1; +} + +[StructLayout(LayoutKind.Sequential, Size = 23, Pack = 2)] +public struct Struct2 +{ + public Struct1 Nest1; +} + +[StructLayout(LayoutKind.Sequential, Size = 87, Pack = 64)] +public struct Struct3 +{ + public Struct1 Nest1; + + public Struct2 Nest2; +} +``` + +To get a collection of fields to access to reach a certain offset within +the type, use the `TryGetFieldPath` method. This method will return +`true` if the offset refers to the beginning of a field, and `false` +otherwise. + +``` csharp +var struct3Definition = (TypeDefinition) Module.LookupMember( + typeof(Struct3).MetadataToken); +var struct3Layout = struct3Definition.GetImpliedMemoryLayout(false); + +uint offset = 20; +bool isStartOfField = layout.TryGetFieldPath(offset, out var path); + +// This results in: +// - isStartOfField: true. +// - path: {Struct3::Nest2, Struct2::Nest1, Struct1::Dummy1}. +``` diff --git a/docs/articles/dotnet/type-signatures.md b/docs/articles/dotnet/type-signatures.md new file mode 100644 index 000000000..e7372d28a --- /dev/null +++ b/docs/articles/dotnet/type-signatures.md @@ -0,0 +1,280 @@ +# Type Signatures + +Type signatures represent references to types within a blob signature. +They are not directly associated with a metadata token, but can +reference types defined in one of the metadata tables. + +All relevant classes in this document can be found in the following +namespaces: + +``` csharp +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; +``` + +## Overview + +Basic leaf type signatures: + +| Type signature name | Example | +|---------------------------------|---------------------------------------------------------| +| `CorLibTypeSignature` | `int32` (`System.Int32`) | +| `TypeDefOrRefSignature` | `System.IO.Stream`, `System.Drawing.Point` | +| `GenericInstanceTypeSignature` | ``System.Collections.Generic.IList`1`` | +| `FunctionPointerTypeSignature` | `method void *(int32, int64)` | +| `GenericParameterSignature` | `!0`, `!!0` | +| `SentinelTypeSignature` | (Used as a delimeter for vararg method signatures) | + +Decorator type signatures: + +| Type signature name | Example | +|--------------------------------|---------------------------------------------------------------------| +| `SzArrayTypeSignature` | `System.Int32[]` | +| `ArrayTypeSignature` | `System.Int32[0.., 0..]` | +| `ByReferenceTypeSignature` | `System.Int32&` | +| `PointerTypeSignature` | `System.Int32*` | +| `CustomModifierTypeSignature` | `System.Int32 modreq (System.Runtime.CompilerServices.IsVolatile)` | +| `BoxedTypeSignature` | (Boxes a value type signature) | +| `PinnedTypeSignature` | (Pins the value of a local variable in memory) | + + +## Basic Element Types + +The CLR defines a set of primitive types (such as `System.Int32`, +`System.Object`) as basic element types that can be referenced using a +single byte, rather than the fully qualified name. These are represented +using the `CorLibTypeSignature` class. + +Every `ModuleDefinition` defines a property called `CorLibTypeFactory`, +which exposes reusable instances of all corlib type signatures: + +``` csharp +ModuleDefinition module = ... +TypeSignature int32Type = module.CorLibTypeFactory.Int32; +``` + +Corlib type signatures can also be looked up by their element type, by +their full name, or by converting a type reference to a corlib type +signature. + +``` csharp +int32Type = module.CorLibTypeFactory.FromElementType(ElementType.I4); +int32Type = module.CorLibTypeFactory.FromName("System", "Int32"); + +var int32TypeRef = new TypeReference(corlibScope, "System", "Int32"); +int32Type = module.CorLibTypeFactory.FromType(int32TypeRef); +``` + +If an invalid element type, name or type descriptor is passed on, these +methods return `null`. + + +## Class and Struct Types + +The `TypeDefOrRefSignature` class is used to reference types in either +the `TypeDef` or `TypeRef` (and sometimes `TypeSpec`) metadata table. + +``` csharp +TypeReference streamTypeRef = new TypeReference(corlibScope, "System.IO", "Stream"); +TypeSignature streamTypeSig = new TypeDefOrRefSignature(streamTypeRef); +``` + +Alternatively, `CreateTypeReference` can be used on any +`IResolutionScope`: + +``` csharp +var streamTypeSig = corlibScope.CreateTypeReference("System.IO", "Stream"); +``` + +> [!WARNING] +> While it is technically possible to reference a basic type such as +> `System.Int32` as a `TypeDefOrRefSignature`, it renders the .NET module +> invalid by most implementations of the CLR. Always use the +> `CorLibTypeSignature` to reference basic types within your blob +> signatures. + + +## Generic Instance Types + +The `GenericInstanceTypeSignature` class is used to instantiate generic +types with type arguments: + +``` csharp +var listTypeRef = new TypeReference(corlibScope, "System.Collections.Generic", "List`1"); + +var listOfString = new GenericInstanceTypeSignature(listTypeRef, + isValueType: false, + typeArguments: new[] { module.CorLibTypeFactory.String }); + +// listOfString now contains a reference to List. +``` + +Alternatively, a generic instance can also be generated via the +`MakeGenericType` fluent syntax method: + +``` csharp +var listOfString = corlibScope + .CreateTypeReference("System.Collections.Generic", "List`1") + .MakeGenericInstanceType(module.CorLibTypeFactory.String); + +// listOfString now contains a reference to List. +``` + +## Function Pointer Types + +Function pointer signatures are strongly-typed pointer types used to +describe addresses to functions or methods. In AsmResolver, they are +represented using a `MethodSignature`: + +``` csharp +var factory = module.CorLibTypeFactory; +var signature = MethodSignature.CreateStatic( + factory.Void, + factory.Int32, + factory.Int32); + +var type = new FunctionPointerTypeSignature(signature); + +// type now contains a reference to `method void *(int32, int32)`. +``` + +Alternatively, a function pointer signature can also be generated via +the `MakeFunctionPointerType` fluent syntax method: + +``` csharp +var factory = module.CorLibTypeFactory; +var type = MethodSignature.CreateStatic( + factory.Void, + factory.Int32, + factory.Int32) + .MakeFunctionPointerType(); + +// type now contains a reference to `method void *(int32, int32)`. +``` + +## Shortcuts + +To quickly transform any `ITypeDescriptor` into a `TypeSignature`, it is +possible to use the `.ToTypeSignature()` method on any +`ITypeDescriptor`. For `TypeReference` s, this will also check whether +the object is referencing a basic type and return the appropriate +`CorLibTypeSignature` instead. + +``` csharp +var streamTypeRef = new TypeReference(corlibScope, "System.IO", "Stream"); +var streamTypeSig = streamTypeRef.ToTypeSignature(); +``` + +Likewise, a `TypeSignature` can also be converted back to a +`ITypeDefOrRef`, which can be referenced using a metadata token, using +the `TypeSignature.ToTypeDefOrRef()` method. + +## Decorating Types + +Type signatures can be annotated with extra properties, such as an array +or pointer specifier. + +Below an example of how to create a type signature referencing +`System.Int32[]`: + +``` csharp +var arrayTypeSig = new SzArrayTypeSignature(module.CorLibTypeFactory.Int32); +``` + +Traversing type signature annotations can be done by accessing the +`BaseType` property of `TypeSignature`. + +``` csharp +var arrayElementType = arrayTypeSig.BaseType; // returns System.Int32 +``` + +Adding decorations to types can also be done through shortcut methods +that follow the `MakeXXX` naming scheme: + +``` csharp +var arrayTypeSig = module.CorLibTypeFactory.Int32.MakeSzArrayType(); +``` + +Below an overview of all factory shortcut methods: + +| Factory method | Description | +|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| `MakeArrayType(int dimensionCount)` | Wraps the type in a new `ArrayTypeSignature` with `dimensionCount` zero based dimensions with no upperbound. | +| `MakeArrayType(ArrayDimension[] dimensions)` | Wraps the type in a new `ArrayTypeSignature` with `dimensions` set as dimensions | +| `MakeByReferenceType()` | Wraps the type in a new `ByReferenceTypeSignature` | +| `MakeModifierType(ITypeDefOrRef modifierType, bool isRequired)` | Wraps the type in a new `CustomModifierTypeSignature` with the specified modifier type. | +| `MakePinnedType()` | Wraps the type in a new `PinnedTypeSignature` | +| `MakePointerType()` | Wraps the type in a new `PointerTypeSignature` | +| `MakeSzArrayType()` | Wraps the type in a new `SzArrayTypeSignature` | +| `MakeGenericInstanceType(TypeSignature[] typeArguments)` | Wraps the type in a new `GenericInstanceTypeSignature` with the provided type arguments. | + + +## Comparing Types + +Type signatures can be tested for semantic equivalence using the +`SignatureComparer` class. Most use-cases of this class will not require +any customization. In these cases, the default `SignatureComparer` can +be used: + +``` csharp +var comparer = SignatureComparer.Default; +``` + +However, if you wish to configure the comparer (e.g., for relaxing some +of the declaring assembly version comparison rules), it is possible to +create a new instance instead: + +``` csharp +var comparer = new SignatureComparer(SignatureComparisonFlags.AllowNewerVersions); +``` + +Once a comparer is obtained, we can test for type equality using any of +the overloaded `Equals` methods: + +``` csharp +TypeSignature type1 = ...; +TypeSignature type2 = ...; + +if (comparer.Equals(type1, type2)) +{ + // type1 and type2 are semantically equivalent. +} +``` + +The `SignatureComparer` class implements various instances of the +`IEqualityComparer` interface, and as such, it can be used as a +comparer for dictionaries and related types: + +``` csharp +var dictionary = new Dictionary(comparer); +``` + +> [!TIP] +> The `SignatureComparer` class also implements equality comparers for +> other kinds of metadata, such as field and method descriptors and their +> signatures. + +In some cases, however, exact type equivalence is too strict of a test. +Since .NET facilitates an object oriented environment, many types will +inherit or derive from each other, making it difficult to pinpoint +exactly which types we would need to compare to test whether two types +are compatible with each other. + +Section I.8.7 of the ECMA-335 specification defines a set of rules that +dictate whether values of a certain type are compatible with or +assignable to variables of another type. These rules are implemented in +AsmResolver using the `IsCompatibleWith` and `IsAssignableTo` methods: + +``` csharp +if (type1.IsCompatibleWith(type2)) +{ + // type1 can be converted to type2. +} +``` + +``` csharp +if (type1.IsAssignableTo(type2)) +{ + // Values of type1 can be assigned to variables of type2. +} +``` diff --git a/docs/articles/dotnet/unmanaged-method-bodies.md b/docs/articles/dotnet/unmanaged-method-bodies.md new file mode 100644 index 000000000..9d931c300 --- /dev/null +++ b/docs/articles/dotnet/unmanaged-method-bodies.md @@ -0,0 +1,244 @@ +# Native Method Bodies + +Method bodies in .NET binaries are not limited to CIL as the +implementation language. Mixed-mode applications can contain methods +implemented using unmanaged code that runs directly on the underlying +processor. Languages might include x86 or ARM, and are always +platform-specific. + +AsmResolver supports creating new method bodies that are implemented +this way. The relevant models in this document can be found in the +following namespaces: + +``` csharp +using AsmResolver.DotNet.Code.Native; +``` + +## Prerequisites + +Before you can start adding native method bodies to a .NET module, a few +prerequisites have to be met. Failing to do so will make the CLR not run +your mixed mode application, and might throw runtime or image format +exceptions, even if everything else conforms to the right format. This +section will go over these requirements briefly. + +### Allowing native code in modules + +To make the CLR treat the output file as a mixed mode application, the +`ILOnly` flag needs to be unset: + +``` csharp +ModuleDefinition module = ... +module.IsILOnly = false; +``` + +Furthermore, make sure the right architecture is specified. For example, +for an x86 64-bit binary, use the following: + +``` csharp +module.PEKind = OptionalHeaderMagic.PE32Plus; +module.MachineType = MachineType.Amd64; +module.IsBit32Required = false; +``` + +For 32-bit x86 binaries, use the following: + +``` csharp +module.PEKind = OptionalHeaderMagic.PE32; +module.MachineType = MachineType.I386; +module.IsBit32Required = true; +``` + +### Flags for native methods + +As per ECMA-335 specification, a method definition can only represent a +native function via Platform Invoke (P/Invoke). While P/Invoke is +usually used for importing functions from external libraries (such as +[kernel32.dll]{.title-ref}), it is also needed for implementing native +methods that are defined within the current .NET module itself. +Therefore, to be able to assign a valid native body to a method, the +right flags need to be set in both the `Attributes` and `ImplAttributes` +property of a `MethodDefinition`: + +``` csharp +MethodDefinition method = ... + +method.Attributes |= MethodAttributes.PInvokeImpl; +method.ImpleAttributes |= MethodImplAttributes.Native | MethodImplAttributes.Unmanaged | MethodImplAttributes.PreserveSig; +``` + +## The NativeMethodBody class + +The `MethodDefinition` class defines a property called +`NativeMethodBody`, which exposes the unmanaged implementation of the +method. + +Each `NativeMethodBody` is assigned to exactly one `MethodDefinition`. +Upon instantiation of such a method body, it is therefore required to +specify the owner of the body: + +``` csharp +MethodDefinition method = ... + +var body = new NativeMethodBody(method); +method.NativeMethodBody = body; +``` + +The `NativeMethodBody` class consists of the following basic building +blocks: + +- `Code`: The raw code stream to be executed. +- `AddressFixups`: A collection of fixups that need to be applied + within the code upon writing the code to the disk. + +In the following sections, we will briefly go over each of them. + +## Writing native code + +The contents of a native method body can be set through the `Code` +property. This is a `byte[]` that represents the raw code stream to be +executed. Below an example of a simple method body written in x86 64-bit +assembly code, that returns the constant `1337`: + +``` csharp +body.Code = new byte[] +{ + 0xb8, 0x39, 0x05, 0x00, 0x00, // mov rax, 1337 + 0xc3 // ret +}; +``` + +> [!NOTE] +> Since native method bodies are platform dependent, AsmResolver does not +> provide a standard way to encode these instructions. To construct the +> byte array that you need for a particular implementation of a method +> body, consider using a third-party assembler or assembler library. + + +## Symbols and Address Fixups + +In a lot of cases, native method bodies that references symbols (such as +imported functions) require direct addresses to be referenced within its +instructions. Since the addresses of these symbols are not known yet +upon creating a `NativeMethodBody`, it is not possible to encode such an +operand directly in the `Code` byte array. To support these kinds of +references regardless, AsmResolver can be instructed to apply address +fixups just before writing the body to the disk. These instructions are +essentially small pieces of information that tell AsmResolver that at a +particular offset the bytes should be replaced with a reference to a +symbol in the final PE. This can be applied to any object that +implements `ISymbol`. In the following, two of the most commonly used +symbols will be discussed. + +### Imported Symbols + +In the PE file format, symbols from external modules are often imported +by placing an entry into the imports directory. This is essentially a +table of names that the Windows PE loader will go through, look up the +actual address of each name, and put it in the import address table. +Typically, when a piece of code is meant to make a call to an external +function, the code will make an indirect call to an entry stored in this +table. In x86 64-bit, using nasm syntax, a call to the `puts` function +might look like the following snippet: + +``` csharp +... +lea rcx, [rel message] +call qword [rel puts] +... +``` + +Consider the following example x86 64-bit code, that is printing the +text `Hello from the unmanaged world!` to the standard output stream +using the `puts` function. + +``` csharp +body.Code = new byte[] +{ + /* 00: */ 0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28 + + /* 04: */ 0x48, 0x8D, 0x0D, 0x10, 0x00, 0x00, 0x00, // lea rcx, [rel message] + /* 0B: */ 0xFF, 0x15, 0x00, 0x00, 0x00, 0x00, // call [rel puts] + + /* 11: */ 0xB8, 0x37, 0x13, 0x00, 0x00, // mov eax, 0x1337 + + /* 16: */ 0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28 + /* 1A: */ 0xC3, // ret + + // message: + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x66, // "Hello f" + 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, // "rom the" + 0x20, 0x75, 0x6e, 0x6d, 0x61, 0x6e, 0x61, // " unmana" + 0x67, 0x65, 0x64, 0x20, 0x77, 0x6f, 0x72, // "ged wor" + 0x6c, 0x64, 0x21, 0x00 // "ld!" +}; +``` + +Notice how the operand of the `call` instruction is left at zero +(`0x00`) bytes. To let AsmResolver know that these 4 bytes are to be +replaced by an address to an entry in the import address table, we first +create a new instance of `ImportedSymbol`, representing the `puts` +symbol: + +``` csharp +var ucrtbased = new ImportedModule("ucrtbased.dll"); +var puts = new ImportedSymbol(0x4fc, "puts"); +ucrtbased.Symbols.Add(puts); +``` + +We can then add it as a fixup to the method body: + +``` csharp +body.AddressFixups.Add(new AddressFixup( + 0xD, AddressFixupType.Relative32BitAddress, puts +)); +``` + +### Local Symbols + +If a native body is supposed to process or return some data that is +defined within the body itself, the `NativeLocalSymbol` class can be +used. + +Consider the following example x86 32-bit snippet, that returns the +virtual address of a string. + +``` csharp +0xB8, 0x00, 0x00, 0x00, 0x00 // mov eax, message +0xc3, // ret + +// message (unicode): +0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00, // "Hello, " +0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x00, 0x00 // "world!." +``` + +Notice how the operand of the `mov` instruction is left at zero (`0x00`) +bytes. To let AsmResolver know that these 4 bytes are to be replaced by +the actual virtual address to `message`, we can define a local symbol +and register an address fixup in the following manner: + +``` csharp +var message = new NativeLocalSymbol(body, offset: 0x6); +body.AddressFixups.Add(new AddressFixup( + 0x1, AddressFixupType.Absolute32BitAddress, message +)); +``` + +> [!NOTE] +> The `NativeLocalSymbol` can only be used within the code of the native +> method body itself. This is due to the fact that these types of symbols +> are not processed further after serializing a `NativeMethodBody` to a +> `CodeSegment` by the default method body serializer. + + +### Fixup Types + +The type of fixup that is required will depend on the architecture and +instruction that is used. Below an overview of all fixups that +AsmResolver is able to apply: + +|Fixup type |Description |Example instructions | +|------------------------|:----------------------------------------------------------------------|---------------------------| +|`Absolute32BitAddress` |The operand is a 32-bit absolute virtual address |`call dword [address]` | +|`Absolute64BitAddress` |The operand is a 64-bit absolute virtual address |`mov rax, address` | +|`Relative32BitAddress` |The operand is an address relative to the current instruction pointer |`call qword [rip+offset]` | diff --git a/docs/articles/faq.md b/docs/articles/faq.md new file mode 100644 index 000000000..4e4806429 --- /dev/null +++ b/docs/articles/faq.md @@ -0,0 +1,96 @@ +# Frequently Asked Questions + +## 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](/articles/peimage/advanced-pe-reading.html#custom-error-handling) (PE) +or [Advanced Module Reading](/articles/dotnet/advanced-module-reading.html#pe-image-reading-parameters) (.NET), +and [Image Builder Diagnostics](/articles/dotnet/advanced-pe-image-building.html#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 +[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). + +## In Mono.Cecil / dnlib my code works, but it does not work in AsmResolver, why? + +Essentially, two things can be happening here: + +- AsmResolver´s code could have a bug. +- You are misusing AsmResolver´s 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). + +## Does AsmResolver have a concept similar to writer events in 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. + +For more details, refer to [Advanced PE Image Building](dotnet/advanced-pe-image-building.md) diff --git a/docs/articles/index.md b/docs/articles/index.md new file mode 100644 index 000000000..851ee02d7 --- /dev/null +++ b/docs/articles/index.md @@ -0,0 +1,8 @@ +# AsmResolver + +This is the documentation of the AsmResolver project. AsmResolver is a +set of libraries allowing .NET programmers to read, modify and write +executable files. This includes .NET as well as native images. The +library exposes high-level representations of the PE, while still +allowing the user to access low-level structures. + diff --git a/docs/articles/overview.md b/docs/articles/overview.md new file mode 100644 index 000000000..229ab9b4c --- /dev/null +++ b/docs/articles/overview.md @@ -0,0 +1,24 @@ +# API Overview + +AsmResolver provides three levels of abstraction of a portable +executable (PE) file. This may sound complicated at first, but a typical +use-case of the library might only need one of them. The three levels +are, in increasing level of abstraction: + +- `PEFile`: The lowest level of abstraction. This layer exposes the + raw top-level PE headers, as well as section headers and raw section + contents. +- `PEImage`: This layer exposes interpretations of data directories, + such as import and export directories, raw .NET metadata structures + and Win32 resources. +- `AssemblyDefinition` or `ModuleDefinition`: (Only relevant to PE + files with .NET metadata) Provides a high-level representation of + the .NET metadata that is somewhat similar to *System.Reflection*. + +The higher level of abstraction you go, the easier the library is to use +and typically the less things you have to worry about. Most people that +use AsmResolver to edit .NET applications will probably never even touch +the `PEFile` or `PEImage` class, as `AssemblyDefinition` is most likely +enough for a lot of use cases. The `PEFile` and `PEImage` classes are +for users that know what they are doing and want to make changes on a +lower level. diff --git a/docs/articles/pdb/basics.md b/docs/articles/pdb/basics.md new file mode 100644 index 000000000..0b7d4bf42 --- /dev/null +++ b/docs/articles/pdb/basics.md @@ -0,0 +1,105 @@ +# Basic I/O + +Every PDB image interaction is done through classes defined by the +`AsmResolver.Symbols.Pdb` namespace: + +``` csharp +using AsmResolver.Symbols.Pdb; +``` + +## Creating a new PDB Image + +Creating a new image can be done by instantiating a `PdbImage` class: + +``` csharp +var image = new PdbImage(); +``` + +## Opening a PDB Image + +Opening a PDB Image can be done through one of the `FromXXX` methods +from the `PdbImage` class: + +``` csharp +byte[] raw = ... +var image = PdbImage.FromBytes(raw); +``` + +``` csharp +var image = PdbImage.FromFile(@"C:\myfile.pdb"); +``` + +``` csharp +MsfFile msfFile = ... +var image = PdbImage.FromFile(msfFile); +``` + +``` csharp +BinaryStreamReader reader = ... +var image = PdbImage.FromReader(reader); +``` + +If you want to read large files (+100MB), consider using memory mapped +I/O instead: + +``` csharp +using var service = new MemoryMappedFileService(); +var image = PdbImage.FromFile(service.OpenFile(@"C:\myfile.pdb")); +``` + +## Writing a PDB Image + +Writing PDB images directly is currently not supported yet, however +there are plans to making this format fully serializable. + +## Creating a new MSF File + +Multi-Stream Format (MSF) files are files that form the backbone +structure of all PDB images. AsmResolver fully supports this lower level +type of access to MSF files using the `MsfFile` class. + +To create a new MSF file, use one of its constructors: + +``` csharp +var msfFile = new MsfFile(); +``` + +``` csharp +var msfFile = new MsfFile(blockSize: 4096); +``` + +## Opening an MSF File + +Opening existing MSF files can be done in a very similar fashion as +reading a PDB Image: + +``` csharp +byte[] raw = ... +var msfFile = MsfFile.FromBytes(raw); +``` + +``` csharp +var msfFile = MsfFile.FromFile(@"C:\myfile.pdb"); +``` + +``` csharp +BinaryStreamReader reader = ... +var msfFile = MsfFile.FromReader(reader); +``` + +Similar to reading PDB images, if you want to read large files (+100MB), +consider using memory mapped I/O instead: + +``` csharp +using var service = new MemoryMappedFileService(); +var msfFile = MsfFile.FromFile(service.OpenFile(@"C:\myfile.pdb")); +``` + +## Writing an MSF File + +Writing an MSF file can be done through one of the `Write` method +overloads. + +``` csharp +msfFile.Write(@"C:\myfile.patched.pdb"); +``` diff --git a/docs/articles/pdb/index.md b/docs/articles/pdb/index.md new file mode 100644 index 000000000..6fb634d7f --- /dev/null +++ b/docs/articles/pdb/index.md @@ -0,0 +1,23 @@ +# Overview + +The Program Database (PDB) file format is a format developed by +Microsoft for storing debugging information about binary files. PDBs are +typically constructed based on the original source code the binary was +compiled with, and lists various symbols that the source code defines +and/or references. + +Since version 5.0, AsmResolver provides a work-in-progress +implementation for reading (and sometimes writing) PDB files to allow +for better analysis of compiled binaries. This implementation is fully +managed, and thus does not depend on libraries such as the Debug +Interface Access (DIA) that only work on the Windows platform. +Furthermore, this project also aims to provide additional documentation +on the file format, to make it more accessible to other developers. + + +> [!WARNING] +> As the PDB file format is not very well documented, and mostly is +> reverse engineered from the official implementation provided by +> Microsoft, not everything in this API is finalized or stable yet. As +> such, this part of AsmResolver\'s API is still likely to undergo some +> breaking changes as development continues. diff --git a/docs/articles/pdb/symbols.md b/docs/articles/pdb/symbols.md new file mode 100644 index 000000000..444a4332f --- /dev/null +++ b/docs/articles/pdb/symbols.md @@ -0,0 +1,113 @@ +# Symbols + +Symbol records define the top-level metadata of a binary, such as public +functions, global fields, compiler information, and user-defined type +definitions. They are stored in either the global symbols stream, or in +individual module streams. + +In the following, we will discuss various methods for accessing symbols +defined in a PDB file. + +## Global Symbols + +The `PdbImage` class defines a `Symbols` property, exposing all globally +defined symbols in the PDB: + +``` csharp +PdbImage image = ...; + +foreach (var symbol in image.Symbols) +{ + Console.WriteLine(symbol); +} +``` + +You can use LINQ\'s collection extensions to obtain symbols of a +specific type. For example, the following iterates over all global +public symbols: + +``` csharp +PdbImage image = ...; + +foreach (var symbol in image.Symbols.Where(s => s.CodeViewSymbolType == CodeViewSymbolType.Pub32)) +{ + Console.WriteLine(symbol); +} +``` + +Most types of symbol are represented by their own dedicated subclass of +`CodeViewSymbol` in AsmResolver. To get a more strongly typed filtering, +use the `OfType()` extension to also automatically cast to the +specific symbol type. The following iterates over all global public +symbols, and automatically casts them to the appropriate `PublicSymbol` +type: + +``` csharp +PdbImage image = ...; + +foreach (var symbol in image.Symbols.OfType()) +{ + Console.WriteLine("Name: {0}, Section: {1}, Offset: {2:X8}.", + symbol.Name, + symbol.SegmentIndex, + symbol.Offset); +} +``` + +> [!NOTE] +> Since this part of the API is a work-in-process, any symbol that is +> currently not supported or recognized by AsmResolver\'s parser will be +> represented by an instance of the `UnknownSymbol` class, exposing the +> raw data of the symbol. + + +## Module Symbols + +PDB images may define symbols that are only valid within the scope of a +single module (typically single compilands and object files). These can +be accessed via the `PdbImage::Modules` property, and each module +defines its own `Symbols` property: + +``` csharp +PdbImage image = ...; + +foreach (var module in image.Modules) +{ + Console.WriteLine(module.Name); + foreach (var symbol in image.Symbols) + Console.WriteLine("\t- {0}", symbol); + Console.WriteLine(); +} +``` + +## Local Symbols + +Records such as the `ProcedureSymbol` class may contain multiple +sub-symbols. These type of symbols all implement `ICodeViewScopeSymbol`, +and define their own set of `Symbols`. This way, the symbol tree can be +traversed recursively. + +Below is an example snippet of obtaining all local variable symbols +defined within the `DllMain` function of a library: + +``` csharp +PdbImage image = ...; + +var module = image.Modules.First(m => m.Name == @"c:\simpledll\release\dllmain.obj"); +var procedure = module.Symbols.OfType().First(p => p.Name == "DllMain"); + +foreach (var local in image.Symbols.OfType()) +{ + Console.WriteLine("Name: {0}, Type: {1}", + local.Name, + local.VariableType); +} +``` + +> [!NOTE] +> In the PDB file format, symbols that define a scope (such as `S_LPROC32` +> records) end their scope with a special `S_END` symbol record in the +> file. However, AsmResolver does **not** include these ending records in +> the list of symbols. Ending records are automatically interpreted and +> inserted when appropriate during the writing process by AsmResolver, and +> should thus not be expected in the list, nor added to the list manually. diff --git a/docs/articles/pefile/basics.md b/docs/articles/pefile/basics.md new file mode 100644 index 000000000..918a96956 --- /dev/null +++ b/docs/articles/pefile/basics.md @@ -0,0 +1,82 @@ +# Basic I/O + +Every raw PE file interaction is done through classes defined by the +`AsmResolver.PE.File` namespace: + +``` csharp +using AsmResolver.PE.File; +``` + +## Creating a new PE file + +Creating a PE file can be done through one of the `PEFile` constructors: + +``` csharp +var peFile = new PEFile(); +``` + +This will create a new empty PE file with 0 sections, and sets some +values in the file header and optional header that are typical for a +32-bit Windows console application targeting the x86 platform. + +## Opening a PE file + +Opening a PE file can be done through one of the `FromXXX` methods: + +``` csharp +byte[] raw = ... +var peFile = PEFile.FromBytes(raw); +``` + +``` csharp +var peFile = PEFile.FromFile(@"C:\myfile.exe"); +``` + +``` csharp +BinaryStreamReader reader = ... +var peFile = PEFile.FromReader(reader); +``` + +By default, AsmResolver assumes the PE file is in its unmapped form. +This is usually the case when files are read directly from the file +system. For memory-mapped PE files, use the overload of the `FromReader` +method, which allows for specifying the memory layout of the input. + +``` csharp +BinaryStreamReader reader = ... +var peFile = PEFile.FromReader(reader, PEMappingMode.Mapped); +``` + +If you want to read large files (+100MB), consider using memory-mapped +I/O instead: + +``` csharp +using var service = new MemoryMappedFileService(); +var peFile = PEFile.FromFile(service.OpenFile(@"C:\myfile.exe")); +``` + +On Windows, if a module is loaded and mapped in memory (e.g. as a native +dependency or by the means of `LoadLibrary`), it is possible to load the +PE file from memory by providing the `HINSTANCE` (a.k.a. module base +address): + +``` csharp +IntPtr hInstance = ... +var peFile = PEFile.FromModuleBaseAddress(hInstance); +``` + +## Writing PE files + +Writing PE files can be done through the `PEFile.Write` method: + +``` csharp +using (var fs = File.Create(@"C:\patched.exe")) +{ + peFile.Write(new BinaryStreamWriter(fs)); +} +``` + +AsmResolver will then reassemble the file with all the changes you made. +Note that this will also recalculate some fields in the headers, such as +`FileHeader.NumberOfSections`. Furthermore, it will also recalculate the +offsets and virtual addresses of each section. diff --git a/docs/articles/pefile/headers.md b/docs/articles/pefile/headers.md new file mode 100644 index 000000000..189394e2b --- /dev/null +++ b/docs/articles/pefile/headers.md @@ -0,0 +1,223 @@ +# PE Headers + +After obtaining an instance of the `PEFile` class, it is possible to +read and edit various properties in the DOS header, COFF file header and +optional header. + +All relevant code for this article is found in the following namespace: + +``` csharp +using AsmResolver.PE.File.Headers; +``` + +## DOS Header + +The DOS header (also known as the MZ header or `IMAGE_DOS_HEADER`) is +the first header in every PE file, and is represented using the +`DosHeader` class in AsmResolver. While the minimal DOS header is 64 +bytes long, and often is followed by a stub of MS DOS code, only one +field is read and used by Windows while preparing the PE file for +execution. This field (`e_lfanew`) is the offset to the NT Headers +(`IMAGE_NT_HEADERS`), which contains the COFF and Optional Header. + +Typically this value is set to `0x80`, but AsmResolver supports reading +and changing this offset if desired: + +``` csharp +PEFile file = ... + +// Obtain e_lfanew: +Console.WriteLine("e_flanew: {0:X8}", file.DosHeader.NextHeaderOffset); + +// Set a new e_lfanew: +file.DosHeader.NextHeaderOffset = 0x100; +``` + +## File Header + +The file header describes general characteristics of the PE file. In +particular, it indicates the target architecture, as well as the total +size of the optional header and number of sections stored in the PE +file. + +AsmResolver exposes the file header via the `PEFile::FileHeader` +property. The properties defined in this object correspond directly with +the fields in `IMAGE_FILE_HEADER` as defined in `winnt.h`, and are both +readable and writeable: + +``` csharp +PEFile file = ... +FileHeader header = file.FileHeader; + +Console.WriteLine($"Machine: {header.Machine}"); +Console.WriteLine("NumberOfSections: {header.NumberOfSections}"); +Console.WriteLine("TimeDateStamp: 0x{header.TimeDateStamp:X8}"); +Console.WriteLine("PointerToSymbolTable: 0x{header.PointerToSymbolTable:X8}"); +Console.WriteLine("NumberOfSymbols: {header.NumberOfSymbols}"); +Console.WriteLine("SizeOfOptionalHeader: 0x{header.SizeOfOptionalHeader:X4}"); +Console.WriteLine("Characteristics: {header.Characteristics}"); +``` + +> [!NOTE] +> While `NumberOfSections` and `SizeOfOptionalHeader` are writeable, these +> properties are automatically updated when using `PEFile::Write` to +> ensure a valid PE file to be written to the disk. + +## Optional Header + +The optional header directly follows the file header of a PE file, and +describes information such as the entry point, as well as file alignment +and target subsystem. It also contains the locations of important data +directories stored in the PE file containing information such as import +address tables and resources. + +AsmResolver exposes the file header via the `PEFile::OptionalHeader` +property. + +``` csharp +PEFile file = ... +OptionalHeader header = file.OptionalHeader; +``` + +### PE32 and PE32+ Format + +While the PE specification defines both a 32-bit and 64-bit version of +the structure, AsmResolver abstracts away the differences using a single +`OptionalHeader` class. The final file format that is used is dictated +by the `Magic` property. Changing the file format can be done by simply +writing to this property: + +``` csharp +// Read currently used file format. +Console.WriteLine($"Magic: {header.Magic}"); + +// Change to PE32+ (64-bit format). +header.Magic = OptionalHeaderMagic.PE32Plus; +``` + +> [!WARNING] +> For a valid PE file, it is important to use the right file format of the +> optional header that matches with the target architecture as specified +> in `FileHeader::Machine`. A 32-bit target architecture will always +> expect a `PE32` file format of the optional header, while a 64-bit +> architecture will require a `PE32Plus` format. AsmResolver does not +> automatically keep these two properties in sync. + +### Entry Point and Data Directories + +The optional header references many segments in the sections of the PE +file via the `AddressOfEntryPoint` and `DataDirectories` properties. + +``` csharp +// Reading the entry point: +Console.WriteLine($"AddressOfEntryPoint: 0x{header.AddressOfEntryPoint:X8}"); + +// Setting a new entry point: +header.AddressOfEntryPoint = 0x12345678; +``` + +Iterating all data directory headers can be done using the following: + +``` csharp +for (int i = 0; i < header.DataDirectories.Count; i++) +{ + var directory = header.DataDirectories[i]; + Console.WriteLine($"[{i}]: RVA = 0x{directory.VirtualAddress:X8}, Size = 0x{directory.Size:X8}"); +} +``` + +Getting or setting a specific data directory header can also be done by +using its symbolic index via `GetDataDirectory` and `SetDataDirectory`: + +``` csharp +// Get the import directory. +var directory = header.GetDataDirectory(DataDirectoryIndex.ImportDirectory); + +// Set the import directory. +header.SetDataDirectory(DataDirectoryIndex.ImportDirectory, new DataDirectory( + virtualAddress: 0x00002000, + size: 0x80 +)); +``` + +Reading the contents behind these pointers can be done e.g., by using +`PEFile::CreateReaderAtRva` or `PEFile::CreateDataDirectoryReader`: + +``` csharp +BinaryStreamReader entryPointReader = file.CreateReaderAtRva(header.AddressOfEntryPoint); +``` + +``` csharp +BinaryStreamReader importsReader = file.CreateDataDirectoryReader( + header.GetDataDirectory(DataDirectoryIndex.ImportDirectory) +); +``` + +These functions throw when an invalid offset or size are provided. It is +also possible to use the `TryCreateXXX` methods instead, to immediately +test for validity and only return the reader if correct information was +provided: + +``` csharp +var importDirectory = header.GetDataDirectory(DataDirectoryIndex.ImportDirectory); +if (file.TryCreateDataDirectoryReader(importDirectory, out var importsReader)) +{ + // Valid RVA and size. Use importReader to read the contents. +} +``` + +### Sub System + +The `SubSystem` field in the optional header defines the type of sub +system the executable will be run in. Typical values are either +`WindowsGui` for graphical applications, and `WindowsCui` for +applications requiring a console window. + +``` csharp +// Reading the target sub system: +Console.WriteLine("SubSystem: {header.SubSystem}"); + +// Changing the application to a GUI application: +header.SubSystem = SubSystem.WindowsGui; +``` + +### Section Alignments + +The optional header defines two properties `FileAlignment` and +`SectionAlignment` that determine the section alignment stored on the +disk and in memory at runtime respectively. + +``` csharp +Console.WriteLine("FileAlignment: 0x{header.FileAlignment}"); +Console.WriteLine("SectionAlignment: 0x{header.SectionAlignment}"); +``` + +AsmResolver respects the value in `FileAlignment` when writing a +`PEFile` object to the disk, and automatically realigns sections when +appropriate. It is also possible to force the realignment of sections to +be done immediately after assigning a new value to these properties +using the `PEFile::AlignSections` method. + +``` csharp +header.FileAlignment = 0x400; +file.AlignSections(); +``` + +See [PE Sections](sections.md) for more information on how to use sections. + +### Other PE Offsets and Sizes + +The optional header defines a few more properties indicating some +important offsets and sizes in the PE file: + +- `SizeOfCode` +- `SizeOfInitializedData` +- `SizeOfUninitializedData` +- `BaseOfCode` +- `BaseOfData` +- `SizeOfImage` +- `SizeOfHeaders` + +These properties can be read and written in the same way other fields +are read and written, but are automatically updated when using +`PEFile::Write` to ensure a valid binary. diff --git a/docs/articles/pefile/index.md b/docs/articles/pefile/index.md new file mode 100644 index 000000000..486a660cc --- /dev/null +++ b/docs/articles/pefile/index.md @@ -0,0 +1,15 @@ +# Overview + +The PE file layer is the lowest level of abstraction of the portable +executable (PE) file format. It\'s main purpose is to read and write raw +executable files from and to the disk. It is mainly represented by the +`PEFile` class, and provides access to the raw top-level PE headers, +including the DOS header, COFF file header and optional header. It also +exposes for each section the section header and raw contents, which can +be read by the means of an `BinaryStreamReader` instance. + +It is important to note that this layer mainly leaves the interpretation +of the data to the user. You will not find any methods or properties +returning models of what is stored in these sections. Interpretations of +e.g. the import directory can be found one layer up, using the `PEImage` +class. diff --git a/docs/articles/pefile/sections.md b/docs/articles/pefile/sections.md new file mode 100644 index 000000000..6734a3016 --- /dev/null +++ b/docs/articles/pefile/sections.md @@ -0,0 +1,123 @@ +# PE Sections + +Sections can be read and modified by accessing the `PEFile.Sections` +property, which is a collection of `PESection` objects. + +``` csharp +PEFile file = ... + +foreach (var section in file.Sections) +{ + Console.WriteLine(section.Name); + Console.WriteLine($"\tFile Offset: 0x{section.Offset:X8}"); + Console.WriteLine($"\tRva: 0x{section.Rva:X8}"); + Console.WriteLine($"\tFile Size: 0x{section.Contents.GetPhysicalSize():X8}"); + Console.WriteLine($"\tVirtual Size: 0x{section.Contents.GetVirtualSize():X8}"); +} +``` + +## Reading Section Data + +Each `PESection` object has a `Contents` property defined of type +`IReadableSegment`. This object is capable of creating a +`BinaryStreamReader` instance to read and parse data from the section: + +``` csharp +var reader = section.CreateReader(); +``` + +If you want to get the entire section in a byte array, you can take the +`ToArray` shortcut: + +``` csharp +byte[] data = section.ToArray(); +``` + +Alternatively, if you have a file offset or RVA to start read from, it +is also possible use one of the `PEFile::CreateReaderAtXXX` methods: + +``` csharp +var reader = file.CreateReaderAtOffset(0x200); +``` + +``` csharp +var reader = file.CreateReaderAtRva(0x2000); +``` + +These methods will automatically find the right section to read from, +and provide a reader that points to the start of this data. + +## Adding a new Section + +The `Sections` property is mutable, which means it is possible to add +new sections and remove others from the PE. New sections can be created +using the `PESection` constructors: + +``` csharp +var section = new PESection(".asmres", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); +section.Contents = new DataSegment(new byte[] {1, 2, 3, 4}); + +file.Sections.Add(section); +``` + +Some sections (such as `.data` or `.bss`) contain uninitialized data, +and might be resized in virtual memory at runtime. As such, the virtual +size of the contents might be different than its physical size. To make +dynamically sized sections, it is possible to use the `VirtualSegment` +to decorate a normal [ISegment]{.title-ref} with a different virtual +size. + +``` csharp +var section = new PESection(".asmres", SectionFlags.MemoryRead | SectionFlags.ContentUninitializedData); +var physicalContents = new DataSegment(new byte[] {1, 2, 3, 4}); +section.Contents = new VirtualSegment(physicalContents, 0x1000); // Create a new segment with a virtual size of 0x1000 bytes. + +file.Sections.Add(section); +``` + +For more advanced section building, see +[Building Sections](/articles/peimage/pe-building.html#building-sections) +[Reading and Writing File Segments](../core/segments.md). + +## Updating Section Offsets + +For performance reasons, offsets and sizes are not computed unless you +explicitly tell AsmResolver to align all sections and update all offsets +within a section. To force a recomputation of all section offsets and +sizes, you can use the `PEFile::AlignSections` method: + +``` csharp +PESection section = ...; +file.Sections.Add(section); + +file.AlignSections(); + +Console.WriteLine("New section RVA: 0x{section.Rva:X8}"); +``` + +If you want to align the sections and also automatically update the +fields in the file and optional header of the PE file, it is also +possible to use `PEFile::UpdateHeaders` instead: + +``` csharp +PESection section = ...; +file.Sections.Add(section); + +file.UpdateHeaders(); + +Console.WriteLine("New section RVA: 0x{section.Rva:X8}"); +Console.WriteLine("New section count: {file.FileHeader.NumberOfSections}"); +``` + +> [!NOTE] +> While both `AlignSections` and `UpdateHeaders` do a traversal of the +> segment tree, they may not update all offsets and sizes stored in the +> sections themselves. When reading a PE file using any of the +> `PEFile::FromXXX`, AsmResolver initializes every section\'s `Contents` +> property with a single contiguous chunk of raw memory, and does not +> parse any of the section contents. As such, if some code or data stored +> in one of these raw section references code or data in another, this +> will not be automatically updated. If, however, the `Contents` property +> is an `ISegment` that does implement `UpdateOffsets` appropriately +> (e.g., when using a `SegmentBuilder`), then all references stored in +> such a segment will be updated accordingly. \ No newline at end of file diff --git a/docs/articles/peimage/advanced-pe-reading.md b/docs/articles/peimage/advanced-pe-reading.md new file mode 100644 index 000000000..cace32c6e --- /dev/null +++ b/docs/articles/peimage/advanced-pe-reading.md @@ -0,0 +1,138 @@ +# Advanced PE Image Reading + +Advanced users might have the need to configure AsmResolver\'s PE image +reader. For example, instead of letting the PE reader throw exceptions +upon reading invalid data, errors should be ignored and recovered from. +Other uses might include a custom interpretation of .NET metadata +streams. These kinds of settings can be configured using the +`PEReaderParameters` class. + +``` csharp +var parameters = new PEReaderParameters(); +``` + +These parameters can then be passed on to any of the `PEImage.FromXXX` +methods. + +``` csharp +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 +errors, set the `ErrorListener` property. There are a couple of default +implementations that AsmResolver provides. + +- `ThrowErrorListener`: Throws the recorded parser exception. This is + the default. +- `EmptyErrorListener`: Silently consumes any parser exception, and + allows the reader to recover. +- `DiagnosticBag`: Collects any parser exceptions, and allows the + reader to recover. + +Below an example on how to use the `DiagnosticBag` for collecting parser +errors and reporting them afterwards. + +``` csharp +var bag = new DiagnosticBag(); + +var image = PEImage.FromFile("...", new PEReaderParameters(bag)); + +/* ... */ + +// Report any errors caught so far to the standard output. +foreach (var error in bag.Exceptions) + Console.WriteLine(error); +``` + +> [!NOTE] +> The `PEImage` class and its derivatives are initialized lazily. As a +> result, parser errors might not immediately appear in the `Exceptions` +> property of the `DiagnosticBag` class. + + +## Custom metadata stream reading + +Some .NET obfuscators insert custom metadata streams in the .NET +metadata directory. By default, AsmResolver creates a +`CustomMetadataStream` object for any metadata stream for which the name +is not recognized. To change this behaviour, you can provide a custom +implementation of the `IMetadataStreamReader` interface, or extend the +existing `DefaultMetadataStreamReader` class. Below an example of an +implementation that changes the way a stream with the name +`#CustomStream` is parsed: + +``` csharp +public class CustomMetadataStreamReader : DefaultMetadataStreamReader +{ + public override IMetadataStream ReadStream( + PEReaderContext context, + MetadataStreamHeader header, + ref BinaryStreamReader reader) + { + if (header.Name == "#CustomStream") + { + // Do custom parsing here. + /* ... */ + } + else + { + // Forward to default stream parser. + base.ReadStream(context, header, ref reader); + } + } +} +``` + +To let the reader use this implementation of the +`IMetadataStreamReader`, set the `MetadataStreamReader` property of the +reader parameters. + +``` csharp +parameters.MetadataStreamReader = new CustomMetadataStreamReader(); +``` + +> [!WARNING] +> Higher levels of abstractions (e.g. `AsmResolver.DotNet`) depend on the +> existence of certain default stream types like the `TablesStream` and +> `StringsStream`. When these are not provided by your custom +> implementation, these abstractions will stop working correctly. + + +## Custom debug data reading + +Debug data directories can have arbitrary data stored in the PE image. +By default, AsmResolver creates for every entry an instance of +`CustomDebugDataSegment`. This can be configured by providing a custom +implementation of the `IDebugDataReader` interface: + +``` csharp +public class CustomDebugDataReader : DefaultDebugDataReader +{ + public override IDebugDataSegment ReadDebugData( + PEReaderContext context, + DebugDataType type, + ref BinaryStreamReader reader) + { + if (type == DebugDataType.Coff) + { + // Do custom parsing here. + /* ... */ + } + else + { + // Forward to default parser. + return base.ReadDebugData(context, type, ref reader); + } + } +} +``` + +To let the reader use this implementation of the `IDebugDataReader`, set +the `DebugDataReader` property of the reader parameters. + +``` csharp +parameters.DebugDataReader = new CustomDebugDataReader(); +``` diff --git a/docs/articles/peimage/basics.md b/docs/articles/peimage/basics.md new file mode 100644 index 000000000..2853fd618 --- /dev/null +++ b/docs/articles/peimage/basics.md @@ -0,0 +1,117 @@ +# Basic I/O + +Every PE image interaction is done through classes defined by the +`AsmResolver.PE` namespace: + +``` csharp +using AsmResolver.PE; +``` + +## Creating a new PE image + +Creating a new image can be done by instantiating a `PEImage` class: + +``` csharp +var peImage = new PEImage(); +``` + +## Opening a PE image + +Opening an image can be done through one of the [FromXXX]{.title-ref} +methods from the `PEImage` class: + +``` csharp +byte[] raw = ... +var peImage = PEImage.FromBytes(raw); +``` + +``` csharp +var peImage = PEImage.FromFile(@"C:\myfile.exe"); +``` + +``` csharp +IPEFile peFile = ... +var peImage = PEImage.FromFile(peFile); +``` + +``` csharp +BinaryStreamReader reader = ... +var peImage = PEImage.FromReader(reader); +``` + +If you want to read large files (+100MB), consider using memory mapped +I/O instead: + +``` csharp +using var service = new MemoryMappedFileService(); +var peImage = PEImage.FromFile(service.OpenFile(@"C:\myfile.exe")); +``` + +On Windows, if a module is loaded and mapped in memory (e.g. as a native +dependency or by the means of `LoadLibrary`), it is possible to load the +PE image from memory by providing the `HINSTANCE` (a.k.a. module base +address): + +``` csharp +IntPtr hInstance = ... +var peImage = PEImage.FromModuleBaseAddress(hInstance); +``` + +## Writing a PE image + +Building an image back to a PE file can be done manually by constructing +a `PEFile`, or by using one of the classes that implement the +`IPEFileBuilder` interface. + +> [!NOTE] +> Currently AsmResolver only provides a full fletched builder for .NET +> images. + +Building a .NET image can be done through the +`AsmResolver.PE.DotNet.Builder.ManagedPEFileBuilder` class: + +``` csharp +var builder = new ManagedPEFileBuilder(); +var newPEFile = builder.CreateFile(image); +``` + +Once a `PEFile` instance has been generated from the image, you can use +it to write the executable to an output stream (such as a file on the +disk or a memory stream). + +``` csharp +using (var stream = File.Create(@"C:\mynewfile.exe")) +{ + var writer = new BinaryStreamWriter(stream); + newPEFile.Write(writer); +} +``` + +For more information on how to construct arbitrary `PEFile` instances +for native images, look at [PE File Building](pe-building.md). + +## Strong name signing + +If the PE image is a .NET image, it can be signed with a strong-name. +Open a strong name private key from a file: + +``` csharp +var snk = StrongNamePrivateKey.FromFile(@"C:\Path\To\keyfile.snk"); +``` + +Make sure that the strong name directory is present and has the correct +size. + +``` csharp +image.DotNetDirectory.StrongName = new DataSegment(new byte[snk.Modulus.Length]); +``` + +After writing the PE image to an output stream, use the +`StrongNameSigner` class to sign the image. + +``` csharp +using Stream outputStream = ... + +var signer = new StrongNameSigner(snk); +signer.SignImage(outputStream, module.Assembly.HashAlgorithm); +``` diff --git a/docs/articles/peimage/debug.md b/docs/articles/peimage/debug.md new file mode 100644 index 000000000..5d24f3bbb --- /dev/null +++ b/docs/articles/peimage/debug.md @@ -0,0 +1,69 @@ +# Debug Directory + +The debug data directory is used in portable executables to store +compiler-generated debug information. In most cases, this information is +a reference to a Program Debug Database (`.pdb`) file. + +The relevant classes for this article are stored in the following +namespace: + +``` csharp +using AsmResolver.PE.Debug; +``` + +## The Debug Data Entries + +The `IPEImage` exposes all debug information through the `DebugData` +property. This is a list of `DebugDataEntry`, providing access to the +type of debug data, as well as the version and raw contents of the data +that is stored. + +``` csharp +foreach (var entry in image.DebugData) +{ + Console.WriteLine("Debug Data Type: {0}", entry.Contents.Type); + Console.WriteLine("Version: {0}.{1}", entry.MajorVersion, entry.MinorVersion); + Console.WriteLine("Data start: {0:X8}", entry.Contents.Rva); +} +``` + +Depending on the type of the debug data entry, the `Contents` property +will be modeled using different implementations of `IDebugDataSegment`. + +> [!NOTE] +> If a PE contains debug data using an unsupported or unrecognized format, +> then the contents will be modeled with a `CustomDebugDataSegment` +> instance instead, which exposes the raw contents as an `ISegment`. + +> [!NOTE] +> Currently, AsmResolver only has rich support for `CodeView` debug data. + +## CodeView Data + +CodeView data is perhaps the most common form of debug data that can +appear in a portable executable. It is emitted by a lot of compilers, +such as the Visual C++ compiler, and is also used by many .NET languages +such as C# and VB.NET. CodeView data is modelled using implementations +of the `CodeViewDataSegment` abstract class. + +``` csharp +if (entry.Contents.Type == DebugDataType.CodeView) +{ + var codeViewData = (CodeViewDataSegment) entry.Contents; + ... +} +``` + +There are various formats used by CodeView data segments, and this +format is decided by the `Signature` property of the +`CodeViewDataSegment` class. The most common format used in a CodeView +segment, is the RSDS segment. This format stores a path to the Program +Debug Database (`*.pdb`) file that is associated to the image. + +``` csharp +if (codeViewData.Signature == CodeViewSignature.Rsds) +{ + var rsdsData = (RsdsDataSegment) data; + Console.WriteLine("PDB Path: {0}", rsdsData.Path); +} +``` diff --git a/docs/articles/peimage/dotnet.md b/docs/articles/peimage/dotnet.md new file mode 100644 index 000000000..351df45a0 --- /dev/null +++ b/docs/articles/peimage/dotnet.md @@ -0,0 +1,343 @@ +# .NET Data Directories + +Managed executables (applications written using a .NET language) contain +an extra data directory in the optional header of the PE file format. +This small data directory contains a header which is also known as the +CLR 2.0 header, and references other structures such as the metadata +directory, raw data for manifest resources and sometimes an extra native +header in the case of mixed mode applications or zapped (ngen\'ed) +applications. + +## .NET directory / CLR 2.0 header + +The .NET data directory can be accessed by the +`IPEImage.DotNetDirectory` property. + +``` csharp +IPEImage peImage = ... + +Console.WriteLine("Managed entry point: {0:X8}", peImage.DotNetDirectory.EntryPoint); +``` + +## Metadata directory + +The metadata data directory is perhaps the most important data directory +that is referenced by the .NET directory. It contains the metadata +streams, such as the table and the blob stream, which play a key role in +the execution of a .NET binary. + +To access the metadata directory, access the `IDotNetDirectory.Metadata` +property, which will provide you an instance of the `IMetadata` +interface: + +``` csharp +var metadata = peImage.DotNetDirectory.Metadata; + +Console.WriteLine("Metadata file format version: {0}.{1}", metadata.MajorVersion, metadata.MinorVersion); +Console.WriteLine("Target .NET runtime version: " + metadata.VersionString); +``` + +## Metadata streams + +The `IMetadata` interface also exposes the `Streams` property, a list of +`IMetadataStream` instances. + +``` csharp +foreach (var stream in metadata.Streams) + Console.WriteLine("Name: " + stream.Name); +``` + +Alternatively, it is possible to get a stream by its name using the +`GetStream(string)` shortcut: + +``` csharp +var stringsStream = metadata.GetStream("#Strings"); +``` + +Or grab the stream by its type: + +``` csharp +var stringsStream = metadata.GetStream(); +``` + +AsmResolver supports parsing streams using the names in the table below. +Any stream with a different name will be converted to a +`CustomMetadataStream`. + +| Name | Class | +|----------------------------|------------------------| +| `#~` `#-` `#Schema` | `TablesStream` | +| `#Blob` | `BlobStream` | +| `#GUID` | `GuidStream` | +| `#Strings` | `StringsStream` | +| `#US` | `UserStringsStream` | + +Some streams support reading the raw contents using a +`BinaryStreamReader`. Effectively, every stream that was read from the +disk is readable in this way. Below is an example of a program that +dumps for each readable stream the contents to a file on the disk: + +``` csharp +// Iterate over all readable streams. +foreach (var stream in metadata.Streams.Where(s => s.CanRead)) +{ + // Create a reader that reads the raw contents of the stream. + var reader = stream.CreateReader(); + + // Write the contents to the disk. + File.WriteAllBytes(stream.Name + ".bin", reader.ReadToEnd()); +} +``` + +The `Streams` property is mutable. You can add new streams, or remove +existing streams: + +``` csharp +// Create a new stream with the contents 1, 2, 3, 4. +var data = new byte[] {1, 2, 3, 4}; +var newStream = new CustomMetadataStream("#Custom", data); + +// Add the stream to the metadata directory. +metadata.Streams.Add(newStream); + +// Remove it again. +metadata.Streams.RemoveAt(metadata.Streams.Count - 1); +``` + +## Blob, Strings, US and GUID streams + +The blob, strings, user-strings and GUID streams are all very similar in +the sense that they all provide a storage for data referenced by the +tables stream. Each of these streams has a very similar API in +AsmResolver. + +| Class | Method | +|---------------------|----------------------| +| `BlobStream` | `GetBlobByIndex` | +| `GuidStream` | `GetGuidByIndex` | +| `StringsStream` | `GetStringByIndex` | +| `UserStringsStream` | `GetStringByIndex` | + +Example: + +``` csharp +var stringsStream = metadata.GetStream(); +string value = stringsStream.GetStringByIndex(0x1234); +``` + +Since blobs in the blob stream have a specific format, just obtaining +the `byte[]` of a blob might not be all that useful. Therefore, the +`BlobStream` has an extra `GetBlobReaderByIndex` method, that allows for +parsing each blob using an `BinaryStreamReader` object instead. If +performance is critical, the `GetBlobReaderByIndex` method is preferred +over `GetBlobByIndex`, as this method also avoids an allocation of a +temporary buffer as well. + +``` csharp +var blobStream = metadata.GetStream(); +if (blobStream.TryGetBlobReaderByIndex(0x1234, out var reader)) +{ + // Use reader to parse the blob signature ... +} +``` + +## Tables stream + +The tables stream (`#~`, `#-` or `#Schema`) is the main stream stored in +the .NET binary. It provides tables for all members defined in the +assembly, as well as all references that the assembly uses. The tables +stream is represented by the `TablesStream` class and can be obtained in +the same way as any other metadata stream: + +``` csharp +TablesStream tablesStream = metadata.GetStream(); +``` + +Metadata tables are represented by the `IMetadataTable` interface. +Individal tables can be accessed using the `GetTable` method: + +```csharp +IMetadataTable typeDefTable = tablesStream.GetTable(TableIndex.TypeDef); +``` + +Tables can also be obtained by their row type: +```csharp +MetadataTable typeDefTable = tablesStream.GetTable(); +``` + +The latter option is the preferred option, as it allows for a more type-safe +interaction with the table as well and avoids boxing of each row in the table. +Each metadata table is associated with its own row structure. Below a table of +all row definitions: + + +| Table index | Name (as per specification) | AsmResolver row structure name | +|-------------|-----------------------------|--------------------------------| +| 0 | Module | `ModuleDefinitionRow` | +| 1 | TypeRef | `TypeReferenceRow` | +| 2 | TypeDef | `TypeDefinitionRow` | +| 3 | FieldPtr | `FieldPointerRow` | +| 4 | Field | `FieldDefinitionRow` | +| 5 | MethodPtr | `MethodPointerRow` | +| 6 | Method | `MethodDefinitionRow` | +| 7 | ParamPtr | `ParameterPointerRow` | +| 8 | Param | `ParameterDefinitionRow` | +| 9 | InterfaceImpl | `InterfaceImplementationRow` | +| 10 | MemberRef | `MemberReferenceRow` | +| 11 | Constant | `ConstantRow` | +| 12 | CustomAttribute | `CustomAttributeRow` | +| 13 | FieldMarshal | `FieldMarshalRow` | +| 14 | DeclSecurity | `SecurityDeclarationRow` | +| 15 | ClassLayout | `ClassLayoutRow` | +| 16 | FieldLayout | `FieldLayoutRow` | +| 17 | StandAloneSig | `StandAloneSignatureRow` | +| 18 | EventMap | `EventMapRow` | +| 19 | EventPtr | `EventPointerRow` | +| 20 | Event | `EventDefinitionRow` | +| 21 | PropertyMap | `PropertyMapRow` | +| 22 | PropertyPtr | `PropertyPointerRow` | +| 23 | Property | `PropertyDefinitionRow` | +| 24 | MethodSemantics | `MethodSemanticsRow` | +| 25 | MethodImpl | `MethodImplementationRow` | +| 26 | ModuleRef | `ModuleReferenceRow` | +| 27 | TypeSpec | `TypeSpecificationRow` | +| 28 | ImplMap | `ImplementatinoMappingRow` | +| 29 | FieldRva | `FieldRvaRow` | +| 30 | EncLog | `EncLogRow` | +| 31 | EncMap | `EncMapRow` | +| 32 | Assembly | `AssemblyDefinitionRow` | +| 33 | AssemblyProcessor | `AssemblyProcessorRow` | +| 34 | AssemblyOS | `AssemblyOSRow` | +| 35 | AssemblyRef | `AssemblyReferenceRow` | +| 36 | AssemblyRefProcessor | `AssemblyRefProcessorRow` | +| 37 | AssemblyRefOS | `AssemblyRefOSRow` | +| 38 | File | `FileReferenceRow` | +| 39 | ExportedType | `ExportedTypeRow` | +| 40 | ManifestResource | `ManifestResourceRow` | +| 41 | NestedClass | `NestedClassRow` | +| 42 | GenericParam | `GenericParamRow` | +| 43 | MethodSpec | `MethodSpecificationRow` | +| 44 | GenericParamConstraint | `GenericParamConstraintRow` } + + +Metadata tables are similar to normal`ICollection\` instances. They provide +enumerators, indexers and methods to add or remove rows from the table. +``` csharp +Console.WriteLine("Number of types: " + typeDefTable.Count); / + +// Get a single row. +TypeDefinitionRow firstTypeRow = typeDefTable[0]; + +// Iterate over all rows: +foreach (var typeRow in typeDefTable) +{ + // ... +} +``` + +Members can also be accessed by their RID using the `GetByRid` or `TryGetByRid` helper functions: + +```csharp +TypeDefinitionRow thirdTypeRow = typeDefTable.GetByRid(3); +``` + +Using the other metadata streams, it is possible to resolve all columns. +Below an example that prints the name and namespace of each type row in the +type definition table in a file. + +```csharp +// Load PE image. +var peImage = PEImage.FromFile(@"C:\file.exe"); +// Obtain relevant streams. +var metadata = peImage.DotNetDirectory.Metadata; +var tablesStream = metadata.GetStream(); +var stringsStream = metadata.GetStream(); + +// Go over each type definition in the file. +var typeDefTable = tablesStream.GetTable(); +foreach (var typeRow in typeDefTable) +{ + // Resolve name and namespace columns using the #Strings stream. + string ns = stringsStream.GetStringByIndex(typeRow.Namespace); + string name = stringsStream.GetStringByIndex(typeRow.Name); + + // Print name and namespace: + Console.WriteLine(string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"); +} +``` +## Method and FieldRVA +Every row structure defined in AsmResolver respects the specification described +by the CLR itself. However, there are two exceptions to this rule, and those are +the **Method** and **FieldRVA** rows. According to the specification, both of +these rows have an **RVA** column that references a segment in the original PE +file. Since this second layer of abstraction attempts to abstract away any file +offset or virtual address, these columns are replaced with properties called +`Body` and `Data` respectively, both of type`ISegmentReference`instead. + +`ISegmentReference`exposes a method`CreateReader()`, which automatically +resolves the RVA that was stored in the row, and creates a new input stream +that can be used to parse e.g. method bodies or field data. + +### Reading method bodies: + +Reading a managed CIL method body can be done using +`CilRawMethodBody.FromReader` method: + +```csharp +var methodTable = tablesStream.GetTable(); +var firstMethod = methodTable[0]; +var methodBody = CilRawMethodBody.FromReader(firstMethod.Body.CreateReader()); +``` + +It is important to note that the user is not bound to use `CilRawMethodBody`. +In the case that the `Native` (`0x0001`) flag is set in +`MethodDefinitionRow.ImplAttributes`, the implementation of the method body is +not written in CIL, but using native code that uses an instruction set dependent +on the platform that this application is targeting. Since the bounds of such a +method body is not always well-defined, AsmResolver does not do any parsing on +its own. However, using the`CreateReader()`method, it is still possible to +decode instructions from this method body, using a custom instruction decoder. + +### Reading field data + +Reading field data can be done in a similar fashion as reading method bodies. +Again use the `CreateReader()` method to gain access to the raw data of the +initial value of the field referenced by a **FieldRVA** row. + +```csharp +var fieldRvaTable = tablesStream.GetTable(); +var firstRva = fieldRvaTable[0]; +var reader = firstRva.Data.CreateReader(); +``` + +### Creating new segment references +Creating new segment references not present in the current PE image yet can be +done using the `ISegment.ToReference()` extension method: + +```csharp +var myData = new DataSegment(new byte[] {1, 2, 3, 4}); +var fieldRva = new FieldRvaRow(myData.ToReference(), 0); +``` + +## TypeReference Hash (TRH) + +Similar to the :ref:`pe-import-hash`, the TypeReference Hash (TRH) can be used +to help identify malware families written in a .NET language. However, unlike +the Import Hash, the TRH is based on the names of all imported type references +instead of the symbols specified in the imports directory of the PE. This is a +more accurate representation for .NET images, as virtually every .NET image +only uses one native symbol (either `mscoree.dll!_CorExeMain` or +`mscoree.dll!_CorDllMain` ). AsmResolver includes a built-in implementation +for this that is based on [the reference implementation provided by GData](https://www.gdatasoftware.com/blog/2020/06/36164-introducing-the-typerefhash-trh). +The hash can be obtained using the `GetTypeReferenceHash` extension method on +`IPEImage`or on `IMetadata`: + +``` csharp +IPEImage image = ... +byte[] hash = image.GetTypeReferenceHash(); +``` + +``` csharp +IMetadata metadata = ... +byte[] hash = metadata.GetTypeReferenceHash(); +``` diff --git a/docs/articles/peimage/exceptions.md b/docs/articles/peimage/exceptions.md new file mode 100644 index 000000000..348bb9768 --- /dev/null +++ b/docs/articles/peimage/exceptions.md @@ -0,0 +1,64 @@ +# Exceptions Directory + +Structured Exception Handling (SEH) in native programming languages such +as C++ are for some platforms implemented using exception handler +tables. These are tables that store ranges of addresses that are +protected by an exception handler. In the PE file format, these tables +are stored in the Exceptions Data Directory. + +The relevant classes for this article are stored in the following +namespace: + +``` csharp +using AsmResolver.PE.Exceptions; +``` + +## Runtime Functions + +The `IPEImage` interface exposes these tables through the `Exceptions` +property. This is of type `IExceptionsDirectory`, which allows for read +access to instances of `IRuntimeFunction` through the `GetEntries` +method. This interface models all the runtime functions through the +method, in a platform agnostic manner. + +``` csharp +foreach (var function in peImage.Exceptions.GetEntries()) +{ + Console.WriteLine("Begin: {0:X8}.", function.Begin.Rva); + Console.WriteLine("End: {0:X8}.", function.End.Rva); +} +``` + +Different platforms use different physical formats for their runtime +functions. To figure out what kind of format an image is using, check +the `Machine` field in the file header of the PE file. + +> [!NOTE] +> Currently AsmResolver only supports reading exception tables of PE files +> targeting the AMD64 (x86 64-bit) architecture. + +## AMD64 / x64 + +AsmResolver provides the `X64ExceptionsDirectory` and +`X64RuntimeFunction` classes that implement the exceptions directory for +the AMD64 (x86 64-bit) architecture. Next to just the start and end +address, this implementation also provides access to the unwind info. + +``` csharp +var directory = (X64ExceptionsDirectory) peImage.Exceptions; + +foreach (var function in directory.Entries) +{ + Console.WriteLine("Begin: {0:X8}.", function.Begin.Rva); + Console.WriteLine("End: {0:X8}.", function.End.Rva); + + var unwindInfo = function.UnwindInfo; + + // Get handler start. + Console.WriteLine("Handler Start: {0:X8}.", unwindInfo.ExceptionHandler.Rva); + + // Read custom SEH data associated to this unwind information. + var dataReader = function.ExceptionHandlerData.CreateReader(); + // ... +} +``` diff --git a/docs/articles/peimage/exports.md b/docs/articles/peimage/exports.md new file mode 100644 index 000000000..dc5600258 --- /dev/null +++ b/docs/articles/peimage/exports.md @@ -0,0 +1,46 @@ +# Exports Directory + +Dynamically linked libraries (DLLs) often expose symbols through +defining exports in the exports directory. + +The `IPEImage` interface exposes the `Exports` property, exposing a +mutable instance of [ExportDirectory]{.title-ref}, which defines the +following properties: + +- `Name`: The name of the dynamically linked library. +- `BaseOrdinal`: The base ordinal of all exported symbols. +- `Entries`: A list of exported symbols. + +Exported symbols are represented using the `ExportedSymbol` class, which +defines: + +- `Name`: The name of the exported symbol. If this value is `null`, + the symbol is exported by ordinal rather than by name. + +- `Ordinal`: The ordinal of the exported symbol. This property is + automatically updated when `BaseOrdinal` of the parent directory is + updated. + +- + + `Address`: A reference to the segment representing the symbol. + + : - For exported functions, this reference points to the first + instruction that is executed. + - For exported fields, this reference points to the first byte + of data that this field consists of. + +## Example + +Below is an example of a program that lists all symbols exported by a +given `IPEImage` instance: + +``` csharp +foreach (var symbol in peImage.Exports.Entries) +{ + Console.WriteLine("Ordinal: " + symbol.Ordinal); + if (symbol.IsByName) + Console.WriteLine("Name: " + symbol.Name); + Console.WriteLine("Address: " + symbol.Address.Rva.ToString("X8")); +} +``` diff --git a/docs/articles/peimage/imports.md b/docs/articles/peimage/imports.md new file mode 100644 index 000000000..77fa00d36 --- /dev/null +++ b/docs/articles/peimage/imports.md @@ -0,0 +1,81 @@ +# Imports Directory + +Most portable executables import functions and fields from other, +external libraries. These are stored in a table in the imports data +directory of the PE file. Each entry in this table defines a module that +is loaded at runtime, and a set of members that are looked up. + +All code relevant to the imports directory of a PE resides in the +following namespace: + +``` csharp +using AsmResolver.PE.Imports; +``` + +## Imported Modules and Symbols + +The `IPEImage` interface exposes the `Imports` property, which contains +all members that are resolved at runtime, grouped by the defining +module. Below an example of a program that lists all members imported by +a given `IPEImage` instance: + +``` csharp +foreach (var module in peImage.Imports) +{ + Console.WriteLine("Module: " + module.Name); + + foreach (var member in module.Symbols) + { + if (member.IsImportByName) + Console.WriteLine("\t- " + member.Name); + else + Console.WriteLine("\t- #" + member.Ordinal); + } + + Console.WriteLine(); +} +``` + +## Import Hash + +An Import Hash (ImpHash) of a PE image is a hash code that is calculated +based on all symbols imported by the image. [Originally introduced in +2014 by +Mandiant](https://www.fireeye.com/blog/threat-research/2014/01/tracking-malware-import-hashing.html), +an Import Hash can be used to help identifying malware families quickly, +as malware samples that belong to the same family often have the same +set of dependencies. + +AsmResolver provides a built-in implementation for calculating the +Import Hash. The hash can be obtained by using the `GetImportHash` +extension method on `IPEImage`: + +``` csharp +IPEImage image = ... +byte[] hash = image.GetImportHash(); +``` + +Since the hash is computed based on the names of all imported symbols, +symbols that are imported by ordinal need to be resolved. By default, +AsmResolver uses a static lookup table for a couple of common Windows +libraries, but should this resolution process be customized, then this +can be done by providing a custom instance of `ISymbolResolver`: + +``` csharp +public class MySymbolResolver : ISymbolResolver +{ + public ExportedSymbol? Resolve(ImportedSymbol symbol) + { + /* Resolve symbol by ordinal here... */ + } +} + +IPEImage image = ... +byte[] hash = image.GetImportHash(new MySymbolResolver()); +``` + +While the Import Hash can be a good identifier for native PE images, for +.NET images this is not the case. .NET images usually only import a +single external symbol (either `mscoree.dll!_CorExeMain` or +`mscoree.dll!_CorDllMain`), and as such they will almost always have the +exact same Import Hash. See [TypeReference Hash (TRH)](/articles/peimage/dotnet.html#typereference-hash-trh) for an alternative for .NET images. diff --git a/docs/articles/peimage/index.md b/docs/articles/peimage/index.md new file mode 100644 index 000000000..c6a38963c --- /dev/null +++ b/docs/articles/peimage/index.md @@ -0,0 +1,7 @@ +# Overview + +The PE image layer is the second layer of abstraction of the portable +executable (PE) file format. It is mostly represented by `IPEImage` and +its derivatives (such as `PEImage`), and works on top of the `PEFile` +layer. Its main purpose is to provide access to mutable models that are +easier to use than the raw data structures the PE file layer exposes. diff --git a/docs/articles/peimage/pe-building.md b/docs/articles/peimage/pe-building.md new file mode 100644 index 000000000..1f7a00f00 --- /dev/null +++ b/docs/articles/peimage/pe-building.md @@ -0,0 +1,194 @@ +# PE File Building + +An `IPEImage` is a higher-level abstraction of an actual PE file, and +hides many of the actual implementation details such as raw section +layout and contents of data directories. While this makes reading and +interpreting a PE very easy, it makes writing a more involved process. + +Unfortunately, the PE file format is not well-structured. Compilers and +linkers have a lot of freedom when it comes to the actual design and +layout of the final PE file. For example, one compiler might place the +Import Address Table (IAT) in a separate section (such as the MSVC), +while others will put it next to the code in the text section (such as +the C# compiler). This degree of freedom makes it virtually impossible +to make a completely generic PE file builder, and as such AsmResolver +does not come with one out of the box. + +It is still possible to build PE files from instances of `IPEImage`, it +might just take more manual labor than you might expect at first. This +article will discuss certain strategies that can be adopted when +constructing new PE files from PE images. + +## Building Sections + +As discussed in [PE Sections](../pefile/sections.md), the `PESection` class +represents a single section in the PE file. This class exposes a property +called `Contents` which is of type `ISegment`. A lot of models in AsmResolver +implement this interface, and as such, these models can directly be used +as the contents of a new section. + +``` csharp +var peFile = new PEFile(); + +var text = new PESection(".text", + SectionFlags.ContentCode | SectionFlags.MemoryRead | SectionFlags.MemoryExecute); +text.Content = new DataSegment(new byte[] { ... } ); +peFile.Sections.Add(text); +``` + +In a lot of cases, however, sections do not consist of a single data +structure, but often comprise many segments concatenated together, +potentially with some padding in between. To compose new segments from a +collection of smaller sub-segments structures, the `SegmentBuilder` +class can be used. This is a class that combines a collection of +`ISegment` instances into one, allowing you to concatenate segments that +belong together easily. Below an example that builds up a `.text` +section that consists of a .NET data directory as well as some native +code. In the example, the native code is aligned to a 4-byte boundary: + +``` csharp +var peFile = new PEFile(); + +var textBuilder = new SegmentBuilder(); +textBuilder.Add(new DotNetDirectory { ... }); +textBuilder.Add(new DataSegment(new byte[] { ... } ), alignment: 4); + +var text = new PESection(".text", + SectionFlags.ContentCode | SectionFlags.MemoryRead | SectionFlags.MemoryExecute); +text.Content = sectionBuilder; + +peFile.Sections.Add(text); +``` + +PE files can also be patched using the `PatchedSegment` API. Below is an +example of patching some data in the `.text` section of a PE File with +some literal data `{1, 2, 3, 4}`, as well as writing an address to a +symbol in the imports table: + +``` csharp +var peFile = PEFile.FromFile("input.exe"); +var section = peFile.Sections.First(s => s.Name == ".text"); + +var someSymbol = peImage + .Imports.First(m => m.Name == "ucrtbased.dll") + .Symbols.First(s => s.Name == "puts"); + +section.Contents = section.Contents.AsPatchedSegment() // Create patched segment. + .Patch(offset: 0x10, data: new byte[] {1, 2, 3, 4}) // Apply literal bytes patch + .Patch(offset: 0x20, AddressFixupType.Absolute64BitAddress, someSymbol); // Apply address fixup patch. +``` + +More detailed information on how to use segments can be found in +[Segments](../core/segments.md). + +## Using complex PE models + +The PE file format defines many complex data structures. While some of +these models are represented in AsmResolver using classes that derive +from `ISegment`, a lot of the classes that represent these data +structures do not implement this interface, and as such cannot be used +as a value for the `Contents` property of a `PESection` directly. This +is due to the fact that most of these models are not required to be one +single entity or chunk of continuous memory within the PE file. Instead, +they are often scattered around the PE file by a compiler. For example, +the Import Directory has a second component the Import Address Table +which is often put in a completely different PE section (usually `.text` +or `.data`) than the Import Directory itself (in `.idata` or `.rdata`). +To make reading and interpreting these data structures more convenient +for the end-user, the `AsmResolver.PE` package adopted some design +choices to abstract these details away to make things more natural to +work with. The downside of this is that writing these structures +requires you to specify where AsmResolver should place these models in +the final PE file. + +In `AsmResolver.PE`, most models for which is the case reside in their +own namespace, and have their own set of classes dedicated to +constructing new segments defined in a `Builder` sub-namespace. For +example, the Win32 resources directory models reside in +`AsmResolver.PE.Win32Resources`, but the actual builder classes are put +in a sub namespace called `AsmResolver.PE.Win32Resources.Builder`. + +``` csharp +IPEImage image = ... + +// Construct a resources directory. +var resources = new ResourceDirectoryBuffer(); +resources.AddDirectory(image.Resources); + +var file = new PEFile(); + +// Place in a read-only section. +var rsrc = new PESection(".rsrc", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); +rsrc.Contents = resources; +file.Sections.Add(rsrc); +``` + +A more complicated structure such as the Imports Directory can be build +like the following: + +``` csharp +IPEImage image = ... + +// Construct an imports directory. +var buffer = new ImportDirectoryBuffer(); +foreach (var module in image.Imports) + buffer.AddModule(module); + +var file = new PEFile(); + +// Place import directory in a read-only section. +var rdata = new PESection(".rdata", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); +rdata.Contents = buffer; +file.Sections.Add(rdata); + +// Place the IAT in a writable section. +var data = new PESection(".data", SectionFlags.MemoryRead | SectionFlags.MemoryWrite | SectionFlags.ContentInitializedData); +data.Contents = buffer.ImportAddressDirectory; +file.Sections.Add(ddata); +``` + +## Using PEFileBuilders + +As a lot of the PE file-building process will be similar for many types +of PE file layouts (such as the construction of the file and optional +headers), AsmResolver comes with a base class called `PEFileBuilderBase` +that abstracts many of these similarities away. Rather than defining and +building up everything yourself, the `PEFileBuilderBase` allows you to +override a couple of methods: + +``` csharp +public class MyPEFileBuidler : PEFileBuilderBase +{ + protected override MyBuilderContext CreateContext(IPEImage image) => new(); + + protected override uint GetFileAlignment(PEFile peFile, IPEImage image, MyBuilderContext context) => 0x200; + + protected override uint GetSectionAlignment(PEFile peFile, IPEImage image, MyBuilderContext context) => 0x2000; + + protected override uint GetImageBase(PEFile peFile, IPEImage image, MyBuilderContext context) => 0x00400000; + + protected override IEnumerable CreateSections(IPEImage image, MyBuilderContext context) + { + /* Create sections here */ + } + + protected override IEnumerable CreateDataDirectories(PEFile peFile, IPEImage image, MyBuilderContext context) + { + /* Create data directories here */ + } +} + +public class MyBuilderContext +{ + /* Define here additional state data to be used in your builder. */ +} +``` + +This can then be used like the following: + +``` csharp +IPEImage image = ... + +var builder = new MyPEFileBuilder(); +PEFile file = builder.CreateFile(image); +``` diff --git a/docs/articles/peimage/tls.md b/docs/articles/peimage/tls.md new file mode 100644 index 000000000..6b6cec285 --- /dev/null +++ b/docs/articles/peimage/tls.md @@ -0,0 +1,82 @@ +# TLS Directory + +Executables that use multiple threads might require static (non-stack) +memory that is local to a specific thread. The PE file format allows for +defining segments of memory within the file that specifies what this +memory should like, and how it should be initialized. This information +is stored inside the Thread Local Storage (TLS) data directory. + +All code relevant to the TLS data directory of a PE resides in the +following namespace: + +``` csharp +using AsmResolver.PE.Tls; +``` + +## Template Data, Zero Fill and Index + +The PE file format defines a segment of memory within the TLS data +directory that specifies how the thread local data should be +initialized. This is represented using the following three properties: + +- `TemplateData`: A segment representing the initial data of the + static memory. +- `SizeOfZeroFill`: The number of extra zeroes appended to the end of + the initial data as specified by `TemplateData` +- `Index`: A reference that will receive the thread index. This is + supposed to be a reference in a writable PE section (typically + `.data`). + +``` csharp +var indexSegment = new DataSegment(new byte[8]); + +var directory = new TlsDirectory +{ + TemplateData = new DataSegment(new byte[] { ... }), + SizeOfZeroFill = 0x1000, + Index = indexSegment.ToReference() +}; +``` + +## TLS Callback Functions + +Next to static initialization data, it is also possible to specify a +list of functions called TLS Callbacks that are supposed to further +initialize the thread local storage. This is exposed through the +`CallbackFunctions` property, which is a list of references to the start +of every TLS callback function. + +``` csharp +for (int i = 0; i < directory.CallbackFunctions.Count; i++) +{ + Console.WriteLine("TLS Callback #{0}: {1:X8}", directory.CallbackFunctions.Rva); +} +``` + +## Creating new TLS directories + +Adding a new TLS directory to an image can be done using the +parameterless constructor of the `TlsDirectory` class: + +``` csharp +var tlsDirectory = new TlsDirectory(); +image.TlsDirectory = tlsDirectory; +``` + +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: + +``` csharp +using AsmResolver.PE.Relocations; + +IPEImage image = ...; + +foreach (var relocation in tlsDirectory.GetRequiredBaseRelocations()) + image.Relocations.Add(relocation); +``` diff --git a/docs/articles/peimage/win32resources.md b/docs/articles/peimage/win32resources.md new file mode 100644 index 000000000..4fa0193dc --- /dev/null +++ b/docs/articles/peimage/win32resources.md @@ -0,0 +1,153 @@ +# Win32 Resources + +Win32 resources are additional files embedded into the PE image, and are +typically stored in the `.rsrc` section. All classes relevant to Win32 +resources can be found in the following namespace: + +``` csharp +using AsmResolver.PE.Win32Resources; +``` + +## Directories + +Resources are exposed by the `IPEImage.Resources` property, which +represents the root directory of all resources stored in the image. +Every directory (including the root directory) is represented by +instances of `IResourceDirectory`. This type contains the `Entries` +property. Entries in a directory can either be another sub directory +containing more entries, or a data entry (an instance of +`IResourceData`) with the raw contents of the resource. + +``` csharp +IPEImage image = ... +IResourceDirectory root = image.Resources; + +foreach (var entry in root.Entries) +{ + if (entry.IsDirectory) + Console.WriteLine("Directory {0}.", entry.Id); + else // if (entry.IsData) + Console.WriteLine("Data {0}.", entry.Id); +} +``` + +Alternatively, you can access specific resources very easily by using +the `GetDirectory` and `GetData`: + +``` csharp +IPEImage image = ... +IResourceData stringDataEntry = image.Resources + .GetDirectory(ResourceType.String) // Get string tables directory. + .GetDirectory(251) // Get string block with ID 251 + .GetData(1033); // Get string with language ID 1033 +``` + +Adding or replacing entries can be by either modifying the `Entries` +property directly, or by using the `AddOrReplace` method. The latter is +recommended as it ensures that an existing entry with the same ID is +replaced with the new one. + +``` csharp +IPEImage image = ... +var newDirectory = new ResourceDirectory(ResourceType.String); +image.Resources.Entries.Add(newDirectory); +``` + +``` csharp +IPEImage image = ... +var newDirectory = new ResourceDirectory(ResourceType.String); +image.Resources.AddOrReplaceEntry(newDirectory); +``` + +Similarly, removing can be done by modifying the `Entries` directory, or +by using the `RemoveEntry` method: + +``` csharp +IPEImage image = ... +image.Resources.RemoveEntry(ResourceType.String); +``` + +## Data Entries + +Data entries are represented using the `IResourceData` interface, and +contain a property called `Contents` which is of type `ISegment`. You +can check if this is a `IReadableSegment`, or use the shortcuts +`CanRead` and `CreateReader` methods instead to read the raw contents of +the entry. + +``` csharp +IPEImage image = ... +IResourceData dataEntry = image.Resources + .GetDirectory(ResourceType.String) // Get string tables directory. + .GetDirectory(251) // Get string block with ID 251 + .GetData(1033); // Get string with language ID 1033 + +if (dataEntry.CanRead) +{ + BinaryStreamReader reader = dataEntry.CreateReader(); + // ... +} +``` + +Adding new data entries can be done by using the `ResourceData` +constructor: + +``` csharp +IPEImage image = ... + +var data = new ResourceData(id: 1033, contents: new DataSegment(new byte[] { ... })); +image.Resources + .GetDirectory(ResourceType.String) + .GetDirectory(251) + .AddOrReplaceEntry(data); +``` + +## Example Traversal + +The following example is a program that dumps the resources tree from a +single PE image. + +``` csharp +private const int IndentationWidth = 3; + +private static void Main(string[] args) +{ + // Open the PE image. + string filePath = args[0].Replace("\"", ""); + var peImage = PEImage.FromFile(filePath); + + // Dump the resources. + PrintResourceDirectory(peImage.Resources); +} + +private static void PrintResourceEntry(IResourceEntry entry, int indentationLevel = 0) +{ + // Decide if we are dealing with a sub directory or a data entry. + if (entry.IsDirectory) + PrintResourceDirectory((IResourceDirectory) entry, indentationLevel); + else if (entry.IsData) + PrintResourcData((IResourceData) entry, indentationLevel); +} + +private static void PrintResourceDirectory(IResourceDirectory directory, int indentationLevel = 0) +{ + string indentation = new string(' ', indentationLevel * IndentationWidth); + + // Print the name or ID of the directory. + string displayName = directory.Name ?? "ID: " + directory.Id; + Console.WriteLine("{0}+- Directory {1}", indentation, displayName); + + // Print all entries in the directory. + foreach (var entry in directory.Entries) + PrintResourceEntry(entry, indentationLevel + 1); +} + +private static void PrintResourcData(IResourceData data, int indentationLevel) +{ + string indentation = new string(' ', indentationLevel * IndentationWidth); + + // Print the name of the data entry, as well as the size of the contents. + string displayName = data.Name ?? "ID: " + data.Id; + Console.WriteLine("{0}+- Data {1} ({2} bytes)", indentation, displayName, data.Contents.GetPhysicalSize()); +} +``` diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml new file mode 100644 index 000000000..8e07653be --- /dev/null +++ b/docs/articles/toc.yml @@ -0,0 +1,93 @@ +items: +- name: Introduction + href: index.md + +- name: General + items: + - name: API Overview + href: overview.md + - name: FAQ + href: faq.md + +- name: Core API + items: + - name: Reading and Writing File Segments + href: core/segments.md + +- name: PE Files + items: + - name: Overview + href: pefile/index.md + - name: Basic I/O + href: pefile/basics.md + - name: PE Headers + href: pefile/headers.md + - name: PE Sections + href: pefile/sections.md + +- name: PE Images + items: + - name: Overview + href: peimage/index.md + - name: Basic I/O + href: peimage/basics.md + - name: Advanced PE Image Reading + href: peimage/advanced-pe-reading.md + - name: Imports Directory + href: peimage/imports.md + - name: Exports Directory + href: peimage/exports.md + - name: Win32 Resources + href: peimage/win32resources.md + - name: Exceptions Directory + href: peimage/exceptions.md + - name: Debug Directory + href: peimage/debug.md + - name: TLS Directory + href: peimage/tls.md + - name: .NET Data Directories + href: peimage/dotnet.md + - name: PE File Building + href: peimage/pe-building.md + +- name: .NET assemblies and modules + items: + - name: Overview + href: dotnet/index.md + - name: Basic I/O + href: dotnet/basics.md + - name: Advanced Module Reading + href: dotnet/advanced-module-reading.md + - name: The Member Tree + href: dotnet/member-tree.md + - name: Type Signatures + href: dotnet/type-signatures.md + - name: Reference Importing + href: dotnet/importing.md + - name: CIL Method Bodies + href: dotnet/managed-method-bodies.md + - name: Native Method Bodies + href: dotnet/unmanaged-method-bodies.md + - name: Dynamic Methods + href: dotnet/dynamic-methods.md + - name: Managed Resources + href: dotnet/managed-resources.md + - name: Member Cloning + href: dotnet/cloning.md + - name: Metadata Token Allocation + href: dotnet/token-allocation.md + - name: Type Memory Layout + href: dotnet/type-memory-layout.md + - name: AppHost / SingleFileHost Bundles + href: dotnet/bundles.md + - name: Advanced PE Image Building + href: dotnet/advanced-pe-image-building.md + +- name: PDB Symbols + items: + - name: Overview + href: pdb/index.md + - name: Basic I/O + href: pdb/basics.md + - name: Symbols + href: pdb/symbols.md diff --git a/docs/core/segments.rst b/docs/core/segments.rst deleted file mode 100644 index b58e0c50e..000000000 --- a/docs/core/segments.rst +++ /dev/null @@ -1,250 +0,0 @@ -.. _segments: - -Reading and Writing File Segments -================================= - -Segments are the basis of everything in AsmResolver. -They are the fundamental building blocks that together make up a binary file (such as a PE file). -Segments are organized as a tree, where the leaves are single contiguous chunk of memory, while the nodes are segments that comprise multiple smaller sub-segments. -The aim of segments is to abstract away the complicated mess that comes with calculating offsets, sizes and updating them accordingly, allowing programmers to easily read binary files, as well as construct new ones. - -Every class that directly translates to a concrete segment in a file on the disk implements the ``ISegment`` interface. -In the following, some of the basics of ``ISegment`` as well as common examples will be introduced. - - -Basic Data Segments -------------------- - -The simplest and arguably the most commonly used form of segment is the ``DataSegment`` class. -This is a class that wraps around a ``byte[]`` into an instance of ``ISegment``, allowing it to be used in any context where a segment are expected in AsmResolver. - -.. code-block:: csharp - - byte[] data = new byte[] { 1, 2, 3, 4 }; - var segment = new DataSegment(data); - - -While the name of the ``DataSegment`` class implies it is used for defining literal data (such as a constant for a variable), it can be used to define *any* type of contiguous memory. -This also includes a raw code stream of a function body and sometimes entire program sections. - - -Reading Segment Contents ------------------------- - -Some implementations of ``ISegment`` (such as ``DataSegment``) allow for reading binary data directly. -Segments that allow for this implement ``IReadableSegment``, which defines a function ``CreateReader`` that can be used to create an instance of ``BinaryStreamReader`` that starts at the beginning of the raw contents of the segment. -This reader can then be used to read the contents of the segment. - -.. code-block:: csharp - - byte[] data = new byte[] { 1, 2, 3, 4 }; - IReadableSegment segment = new DataSegment(data); - - var reader = segment.CreateReader(); - reader.ReadByte(); // returns 1 - reader.ReadByte(); // returns 2 - reader.ReadByte(); // returns 3 - reader.ReadByte(); // returns 4 - reader.ReadByte(); // throws EndOfStreamException. - - -Alternatively, a ``IReadableSegment`` can be turned into a ``byte[]`` quickly using the ``ToArray()`` method. - -.. code-block:: csharp - - byte[] data = new byte[] { 1, 2, 3, 4 }; - IReadableSegment segment = new DataSegment(data); - - byte[] allData = segment.ToArray(); // Returns { 1, 2, 3, 4 } - - -Composing new Segments ----------------------- - -Many segments comprise multiple smaller sub-segments. -For example, PE sections often do not contain just a single data structure, but are a collection of structures concatenated together. -To facilitate more complicated structures like these, the ``SegmentBuilder`` class can be used to combine ``ISegment`` instances into one effortlessly: - -.. code-block:: csharp - - var builder = new SegmentBuilder(); - - builder.Add(new DataSegment(...)); - builder.Add(new DataSegment(...)); - - -Many segments in an executable file format require segments to be aligned to a certain byte-boundary. -The ``SegmentBuilder::Add`` method allows for specifying this alignment, and automatically adjust the offsets and sizes accordingly: - -.. code-block:: csharp - - var builder = new SegmentBuilder(); - - // Add some segment with potentially a size that is not a multiple of 4 bytes. - builder.Add(new DataSegment(...)); - - // Ensure the next segment is aligned to a 4-byte boundary in the final file. - builder.Add(new DataSegment(...), alignment: 4); - - -Since ``SegmentBuilder`` implements ``ISegment`` itself, it can also be used within another ``SegmentBuilder``, allowing for recursive constructions like the following: - -.. code-block:: csharp - - var child = new SegmentBuilder(); - child.Add(new DataSegment(...)); - child.Add(new DataSegment(...)); - - var root = new SegmentBuilder(); - root.Add(new DataSegment(...)); - root.Add(child); // Nest segment builders into each other. - - -Resizing Segments at Runtime ----------------------------- - -Most segments in an executable file retain their size at runtime. -However, some segments (such as a ``.bss`` section in a PE file) may be resized upon mapping it into memory. -AsmResolver represents these segments using the ``VirtualSegment`` class: - -.. code-block:: csharp - - var physicalContents = new DataSegment(new byte[] {1, 2, 3, 4}); - section.Contents = new VirtualSegment(physicalContents, 0x1000); // Create a new segment with a virtual size of 0x1000 bytes. - - -Patching Segments ------------------ - -Some use-cases of AsmResolver require segments to be hot-patched with new data after serialization. -This is done via the ``PatchedSegment`` class. - -Any segment can be wrapped into a ``PatchedSegment`` via its constructor: - -.. code-block:: csharp - - using AsmResolver.Patching; - - ISegment segment = ... - var patchedSegment = new PatchedSegment(segment); - - -Alternatively, you can use (the preferred) fluent syntax: - -.. code-block:: csharp - - using AsmResolver.Patching; - - ISegment segment = ... - var patchedSegment = segment.AsPatchedSegment(); - - -Applying the patches can then be done by repeatedly calling one of the ``Patch`` method overloads. -Below is an example of patching a section within a PE file: - -.. code-block:: csharp - - var peFile = PEFile.FromFile("input.exe"); - var section = peFile.Sections.First(s => s.Name == ".text"); - - var someSymbol = peImage - .Imports.First(m => m.Name == "ucrtbased.dll") - .Symbols.First(s => s.Name == "puts"); - - section.Contents = section.Contents.AsPatchedSegment() // Create patched segment. - .Patch(offset: 0x10, data: new byte[] {1, 2, 3, 4}) // Apply literal bytes patch - .Patch(offset: 0x20, AddressFixupType.Absolute64BitAddress, someSymbol); // Apply address fixup patch. - - -The patching API can be extended by implementing the ``IPatch`` yourself. - - -Calculating Offsets and Sizes ------------------------------ - -Typically, the ``ISegment`` API aims to abstract away any raw offset, relative virtual address (RVA), and/or size of a data structure within a binary file. -However, in case the final offset and/or size of a segment still need to be determined and used (e.g., when implementing new segments), it is important to understand how this is done. - -Two properties are responsible for representing the offsets: - -- ``Offset``: The starting file or memory address of the segment. -- ``Rva``: The virtual address of the segment, relative to the executable's image base at runtime. - - -Typically, these properties are read-only and managed by AsmResolver itself. -However, to update the offsets and RVAs of a segment, you can call the ``UpdateOffsets`` method. -This method traverses the entire segment recursively, and updates the offsets accordingly. - -.. code-block:: csharp - - ISegment segment = ... - - // Relocate a segment to an offsets-rva pair: - segment.UpdateOffsets(new RelocationParameters(offset: 0x200, rva: 0x2000); - - Console.WriteLine("Offset: 0x{0:X8}", segment.Offset); // Prints 0x200 - Console.WriteLine("Rva: 0x{0:X8}", segment.Rva); // Prints 0x2000 - -.. warning:: - - Try to call ``UpdateOffsets()`` as sparsely as possible. - The method does a full pass on the entire segment, and updates all offsets of all sub-segments as well. - It can thus be very inefficient to call them repeatedly. - - -The size (in bytes) of a segment can be calculated using either the ``GetPhysicalSize()`` or ``GetVirtualSize()``. -Typically, these two measurements are going to be equal, but for some segments (such as a ``VirtualSegment``) this may differ: - -.. code-block:: csharp - - ISegment segment = ... - - // Measure the size of the segment: - uint physicalSize = segment.GetPhysicalSize(); - uint virtualSize = segment.GetVirtualSize(); - - Console.WriteLine("Physical (File) Size: 0x{0:X8}", physicalSize); - Console.WriteLine("Virtual (Runtime) Size: 0x{0:X8}", virtualSize); - - -.. warning:: - - Only call ``GetPhysicalSize()`` and ``GetVirtualSize()`` whenever you know the offsets of the segment are up to date. - Due to padding requirements, many segments will have a slightly different size depending on the final file offset they are placed at. - - -.. warning:: - - Try to call ``GetPhysicalSize()`` and ``GetVirtualSize()`` as sparsely as possible. - These methods do a full pass on the entire segment, and measure the total amount of bytes required to represent it. - It can thus be very inefficient to call them repeatedly. - - -Serializing Segments --------------------- - -Segments are serialized using the ``ISegment::Write`` method. - -.. code-block:: csharp - - ISegment segment = ... - - using var stream = new MemoryStream(); - segment.Write(new BinaryStreamWriter(stream)); - - byte[] serializedData = stream.ToArray(); - - -Alternatively, you can quickly serialize a segment to a ``byte[]`` using the ``WriteIntoArray()`` extension method: - -.. code-block:: csharp - - ISegment segment = ... - - byte[] serializedData = stream.WriteIntoArray(); - - -.. warning:: - - Only call ``Write`` whenever you know the offsets of the segment are up to date. - Many segments will contain offsets to other segments in the file, which may not be accurate until all offsets are calculated. diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 000000000..c2b9a48af --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,58 @@ +{ + "metadata": [ + { + "src": [ + { + "files": [ + "**/*.csproj" + ], + "src": "../src" + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "separatePages", + "allowCompilationErrors": false + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "articles/**.md", + "articles/**/toc.yml", + "toc.yml", + "*.md" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "output": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "default", + "modern", + "my-template" + ], + "postProcessors": [], + "keepFileLink": false, + "disableGitFeatures": false + } +} diff --git a/docs/dotnet/advanced-module-reading.rst b/docs/dotnet/advanced-module-reading.rst deleted file mode 100644 index e84ff94aa..000000000 --- a/docs/dotnet/advanced-module-reading.rst +++ /dev/null @@ -1,156 +0,0 @@ -.. _dotnet-advanced-module-reading: - -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 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. - -.. code-block:: csharp - - var parameters = new ModuleReaderParameters(); - -These parameters can then be passed on to any of the ``ModuleDefinition.FromXXX`` methods. - -.. code-block:: csharp - - var module = ModuleDefinition.FromFile(@"C:\Path\To\File.exe", parameters); - - -PE image reading parameters ---------------------------- - -.NET modules are stored in a normal PE file. To customize the way AsmResolver reads the underlying PE image before it is being interpreted as a .NET image, ``ModuleReaderParameters`` provides a ``PEReaderParameters`` property that can be modified or replaced completely. - -.. code-block:: csharp - - parameters.PEReaderParameters = new PEReaderParameters - { - ... - }; - -For example, this can be in particular useful if you want to let AsmResolver ignore and recover from invalid data in the input file: - -.. code-block:: csharp - - parameters.PEReaderParameters.ErrorListener = EmptyErrorListener.Instance; - - -Alternatively, this property can also be set through the constructor of the ``ModuleReaderParameters`` class directly: - -.. code-block:: csharp - - var parameters = new ModuleReaderParameters(EmptyErrorListener.Instance); - -For more information on customizing the underlying PE image reading process, see :ref:`pe-advanced-image-reading`. - - -Changing working directory --------------------------- - -Modules often depend on other assemblies. These assemblies often are placed in the same directory as the original module. However, should this not be the case, it is possible to change the path of the working directory of the resolvers. - -.. code-block:: csharp - - parameters.WorkingDirectory = @"C:\Path\To\Different\Folder"; - - -Alternatively, this property can also be set through the constructor of the ``ModuleReaderParameters`` class directly: - -.. code-block:: csharp - - var parameters = new ModuleReaderParameters(@"C:\Path\To\Different\Folder"); - - -Custom .netmodule resolvers ---------------------------- - -For multi-module assemblies, AsmResolver looks into the path stored in ``WorkingDirectory`` for files with the .netmodule extension by default. If it is necessary to change this behaviour, it is possible to provide a custom implementation of the ``INetModuleResolver`` interface. - -.. code-block:: csharp - - public class CustomNetModuleResolver : INetModuleResolver - { - public ModuleDefinition Resolve(string name) - { - // ... - } - } - -To let the reader use this implementation of the ``INetModuleResolver``, set the ``NetModuleResolver`` property of the reader parameters. - -.. code-block:: csharp - - parameters.NetModuleResolver = new CustomNetModuleResolver(); - - -Custom method body readers --------------------------- - -Some .NET obfuscators store the implementation of method definitions in an encrypted form, use native method bodies, or use a custom format that is interpreted at runtime by the means of JIT hooking. To change the way of how method bodies are being read, it is possible to provide a custom implementation of the ``IMethodBodyReader`` interface, or extend the default implementation. - -Below an example of how to add support for reading simple x86 method bodies: - -.. code-block:: csharp - - public class CustomMethodBodyReader : DefaultMethodBodyReader - { - public override MethodBody ReadMethodBody( - ModuleReaderContext context, - MethodDefinition owner, - in MethodDefinitionRow row) - { - if (owner.IsNative && row.Body.CanRead) - { - // Create raw binary reader if method is native. - var reader = row.Body.CreateReader(); - - // Read until the first occurrence of a ret instruction (opcode 0xC3). - // Note: This is for demonstration purposes only, and is by no means - // a very accurate heuristic for finding the boundaries of native - // method bodies. - - var code = reader.ReadBytesUntil(0xC3); - - // Create native method body. - return new NativeMethodBody(owner, code); - } - - // Off-load to default implementation. - return base.ReadMethodBody(context, owner, row); - } - } - - -To let the reader use this implementation of the ``IMethodBodyReader``, set the ``MethodBodyReader`` property of the reader parameters. - -.. code-block:: csharp - - parameters.MethodBodyReader = new CustomMethodBodyReader(); - - -Custom Field RVA reading ------------------------- - -By default, the field RVA data storing the initial binary value of a field is interpreted as raw byte blobs, and are turned into instances of the ``DataSegment`` class. To adjust this behaviour, it is possible to provide a custom implementation of the ``IFieldRvaDataReader`` interface. - - -.. code-block:: csharp - - public class CustomFieldRvaDataReader : FieldRvaDataReader - { - public override ISegment ResolveFieldData( - IErrorListener listener, - Platform platform, - IDotNetDirectory directory, - in FieldRvaRow fieldRvaRow) - { - // ... - } - } - - -To let the reader use this implementation of the ``IFieldRvaDataReader``, set the ``FieldRvaDataReader`` property of the reader parameters. - -.. code-block:: csharp - - parameters.FieldRvaDataReader = new CustomFieldRvaDataReader(); diff --git a/docs/dotnet/advanced-pe-image-building.rst b/docs/dotnet/advanced-pe-image-building.rst deleted file mode 100644 index 8a5801013..000000000 --- a/docs/dotnet/advanced-pe-image-building.rst +++ /dev/null @@ -1,280 +0,0 @@ -.. _dotnet-advanced-pe-image-building: - -Advanced PE Image Building -========================== - -The easiest way to write a .NET module to the disk is by using the ``Write`` method: - -.. code-block:: csharp - - module.Write(@"C:\Path\To\Output\Binary.exe"); - - -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 that takes instances of ``IPEImageBuilder`` instead: - -.. code-block:: csharp - - var imageBuilder = new ManagedPEImageBuilder(); - - /* Configuration of imageBuilder here... */ - - module.Write(@"C:\Path\To\Output\Binary.exe", imageBuilder); - - -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 - - var imageBuilder = new ManagedPEImageBuilder(); - - /* Configuration of imageBuilder here... */ - - // Construct image. - var result = imageBuilder.CreateImage(module); - - /* Inspect build result ... */ - - // Obtain constructed PE image. - var image = result.ConstructedImage; - - /* Post processing of image happens here... */ - - // Write image to the disk. - var fileBuilder = new ManagedPEFileBuilder(); - var file = fileBuilder.CreateFile(image); - file.Write(@"C:\Path\To\Output\Binary.exe"); - - -This article explores various features about the ``ManagedPEImageBuilder`` class. - - -Token mappings --------------- - -Upon constructing a new PE image for a module, members defined in the module might be re-ordered. This can make post-processing of the PE image difficult, as metadata members cannot be looked up by their original metadata token anymore. The ``PEImageBuildResult`` object returned by ``CreateImage`` defines a property called ``TokenMapping``. This object maps all members that were included in the construction of the PE image to the newly assigned metadata tokens, allowing for new metadata rows to be looked up easily and efficiently. - -.. code-block:: csharp - - var mainMethod = module.ManagedEntrypointMethod; - - // Build PE image. - var result = imageBuilder.CreateImage(module); - - // Look up the new metadata row assigned to the main method. - var newToken = result.TokenMapping[mainMethod]; - var mainMethodRow = result.ConstructedImage.DotNetDirectory.Metadata - .GetStream() - .GetTable() - .GetByRid(newToken.Rid); - - -Preserving raw metadata structure ---------------------------------- - -Some .NET modules are carefully crafted and rely on the raw structure of all metadata streams. These kinds of modules often rely on one of the following: - -- RIDs of rows within a metadata table. -- Indices of blobs within the ``#Blob``, ``#Strings``, ``#US`` or ``#GUID`` heaps. -- Unknown or unconventional metadata streams and their order. - -The default PE image builder for .NET modules (``ManagedPEImageBuilder``) defines a property called ``DotNetDirectoryFactory``, which contains the object responsible for constructing the .NET data directory, can be configured to preserve as much of this structure as possible. With the help of the ``MetadataBuilderFlags`` enum, it is possible to indicate which structures of the metadata directory need to preserved. The following table provides an overview of all preservation metadata builder flags that can be used and combined: - -+----------------------------------------+-------------------------------------------------------------------+ -| flag | Description | -+========================================+===================================================================+ -| ``PreserveXXXIndices`` | Preserves all row indices of the original ``XXX`` metadata table. | -+----------------------------------------+-------------------------------------------------------------------+ -| ``PreserveTableIndices`` | Preserves all row indices from all original metadata tables. | -+----------------------------------------+-------------------------------------------------------------------+ -| ``PreserveBlobIndices`` | Preserves all blob indices in the ``#Blob`` stream. | -+----------------------------------------+-------------------------------------------------------------------+ -| ``PreserveGuidIndices`` | Preserves all GUID indices in the ``#GUID`` stream. | -+----------------------------------------+-------------------------------------------------------------------+ -| ``PreserveStringIndices`` | Preserves all string indices in the ``#Strings`` stream. | -+----------------------------------------+-------------------------------------------------------------------+ -| ``PreserveUserStringIndices`` | Preserves all user-string indices in the ``#US`` stream. | -+----------------------------------------+-------------------------------------------------------------------+ -| ``PreserveUnknownStreams`` | Preserves any of the unknown / unconventional metadata streams. | -+----------------------------------------+-------------------------------------------------------------------+ -| ``PreserveStreamOrder`` | Preserves the original order of all metadata streams. | -+----------------------------------------+-------------------------------------------------------------------+ -| ``PreserveAll`` | Preserves as much of the original metadata as possible. | -+----------------------------------------+-------------------------------------------------------------------+ - - -Below is an example on how to configure the image builder to preserve blob data and all metadata tokens to type references: - -.. code-block:: csharp - - var factory = new DotNetDirectoryFactory(); - factory.MetadataBuilderFlags = MetadataBuilderFlags.PreserveBlobIndices - | MetadataBuilderFlags.PreserveTypeReferenceIndices; - imageBuilder.DotNetDirectoryFactory = factory; - - -.. warning:: - - Preserving heap indices copies over the original contents of the heaps to the new PE image "as-is". While AsmResolver tries to reuse blobs defined in the original heaps as much as possible, this is often not possible without also preserving RIDs in the tables stream. This might result in a significant increase in file size. - -.. note:: - - Preserving RIDs within metadata tables might require AsmResolver to inject placeholder rows in existing metadata tables that are solely there to fill up space between existing rows. - -.. warning:: - - Preserving RIDs within metadata tables might require AsmResolver to make use of the Edit-And-Continue metadata tables (such as the pointer tables). The resulting tables stream could therefore be renamed from ``#~`` to ``#-``, and the file size might increase. - - -String folding in #Strings stream ---------------------------------- - -Named metadata members (such as types, methods and fields) are assigned a name by referencing a string in the ``#Strings`` stream by its starting offset. When a metadata member has a name that is a suffix of another member's name, then it is possible to only store the longer name in the ``#Strings`` stream, and let the member with the shorter name use an offset within the middle of this longer name. For example, consider two members with the names ``ABCDEFG`` and ``DEFG``. If ``ABCDEFG`` is stored at offset ``1``, then the name ``DEFG`` is implicitly defined at offset ``1 + 3 = 4``, and can thus be referenced without appending ``DEFG`` to the stream a second time. - -By default, the PE image builder will fold strings in the ``#Strings`` stream as described in the above. However, for some input binaries, this might make the building process take a significant amount of time. Therefore, to disable this folding of strings, specify the ``NoStringsStreamOptimization`` flag in your ``DotNetDirectoryFactory``: - -.. code-block:: csharp - - factory.MetadataBuilderFlags |= MetadataBuilderFlags.NoStringsStreamOptimization; - - -.. warning:: - Some obfuscated binaries might include lots of members that have very long but similar names. For these types of binaries, disabling this optimization can result in a significantly larger output file size. - - -.. note:: - - When ``PreserveStringIndices`` is set and string folding is enabled (``NoStringsStreamOptimization`` is unset), the PE image builder will not fold strings from the original ``#Strings`` stream into each other. However, it will still try to reuse these original strings as much as possible. - - -Preserving maximum stack depth ------------------------------- - -CIL method bodies work with a stack, and the stack has a pre-defined size. This pre-defined size is defined by the ``MaxStack`` property of the ``CilMethodBody`` class. By default, AsmResolver automatically calculates the maximum stack depth of a method body upon writing the module to the disk. However, this is not always desirable. - -To override this behaviour, set ``ComputeMaxStackOnBuild`` to ``false`` on all method bodies to exclude in the maximum stack depth calculation. - -Alternatively, if you want to force the maximum stack depths should be either preserved or recalculated, it is possible to provide a custom implemenmtation of the ``IMethodBodySerializer``, or configure the ``CilMethodBodySerializer``. - -Below an example on how to preserve maximum stack depths for all methods in the assembly: - -.. code-block:: csharp - - DotNetDirectoryFactory factory = ...; - factory.MethodBodySerializer = new CilMethodBodySerializer - { - ComputeMaxStackOnBuildOverride = false - } - - -.. warning:: - - Disabling max stack computation may have unexpected side-effects (such as rendering certain CIL method bodies invalid). - - - -Strong name signing -------------------- - -Assemblies can be signed with a strong-name signature. Open a strong name private key from a file: - -.. code-block:: csharp - - var snk = StrongNamePrivateKey.FromFile(@"C:\Path\To\keyfile.snk"); - -Prepare the image builder to delay-sign the PE image: - -.. code-block:: csharp - - DotNetDirectoryFactory factory = ...; - factory.StrongNamePrivateKey = snk; - -After writing the module to an output stream, use the ``StrongNameSigner`` class to sign the image. - -.. code-block:: csharp - - using Stream outputStream = ... - module.Write(outputStream, factory); - - var signer = new StrongNameSigner(snk); - signer.SignImage(outputStream, module.Assembly.HashAlgorithm); - - -.. _dotnet-image-builder-diagnostics: - -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 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 - - // Set up a diagnostic bag as an error listener. - var diagnosticBag = new DiagnosticBag(); - imageBuilder.ErrorListener = diagnosticBag; - - // Build image. - var result = imageBuilder.CreateImage(module); - - // Print all errors. - 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 simply build the PE image ignoring all diagnostic errors, it is also possible to pass in ``EmptyErrorListener.Instance`` instead: - -.. code-block:: csharp - - 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"); - } - - diff --git a/docs/dotnet/basics.rst b/docs/dotnet/basics.rst deleted file mode 100644 index 9ff969072..000000000 --- a/docs/dotnet/basics.rst +++ /dev/null @@ -1,153 +0,0 @@ -Basic I/O -========= - -Every .NET image interaction is done through classes defined by the ``AsmResolver.DotNet`` namespace: - -.. code-block:: csharp - - using AsmResolver.DotNet; - -Creating a new .NET module --------------------------- - -Creating a new image can be done by instantiating a ``ModuleDefinition`` class: - -.. code-block:: csharp - - var module = new ModuleDefinition("MyModule.exe"); - -The above will create a module that references mscorlib.dll 4.0.0.0 (.NET Framework 4.0). If another version of the Common Object Runtime Library is desired, we can use one of the overloads of the constructor, and use a custom ``AssemblyReference``, or one of the pre-defined assembly references in the ``KnownCorLibs`` class to target another version of the library. - -.. code-block:: csharp - - var module = new ModuleDefinition("MyModule.exe", KnownCorLibs.SystemRuntime_v4_2_2_0); - - -Opening a .NET module ---------------------- - -Opening a .NET module can be done through one of the ``FromXXX`` methods from the ``ModuleDefinition`` class: - -.. code-block:: csharp - - byte[] raw = ... - var module = ModuleDefinition.FromBytes(raw); - -.. code-block:: csharp - - var module = ModuleDefinition.FromFile(@"C:\myfile.exe"); - -.. code-block:: csharp - - PEFile peFile = ... - var module = ModuleDefinition.FromFile(peFile); - -.. code-block:: csharp - - BinaryStreamReader reader = ... - var module = ModuleDefinition.FromReader(reader); - -.. code-block:: csharp - - IPEImage peImage = ... - var module = ModuleDefinition.FromImage(peImage); - - -If you want to read large files (+100MB), consider using memory mapped I/O instead: - -.. code-block:: csharp - - using var service = new MemoryMappedFileService(); - var module = ModuleDefinition.FromFile(service.OpenFile(@"C:\myfile.exe")); - - -On Windows, if a module is loaded and mapped in memory (e.g. as a dependency defined in Metadata or by the means of ``System.Reflection``), it is possible to load the module from memory by using ``FromModule``, or by transforming the module into a ``HINSTANCE`` and then providing it to the ``FromModuleBaseAddress`` method: - -.. code-block:: csharp - - Module module = ...; - var module = ModuleDefinition.FromModule(module); - - -.. code-block:: csharp - - Module module = ...; - IntPtr hInstance = Marshal.GetHINSTANCE(module); - var module = ModuleDefinition.FromModuleBaseAddress(hInstance); - - -Writing a .NET module ---------------------- - -Writing a .NET module can be done through one of the ``Write`` method overloads. - -.. code-block:: csharp - - module.Write(@"C:\myfile.patched.exe"); - -.. code-block:: csharp - - Stream stream = ...; - module.Write(stream); - -For more advanced options to write .NET modules, see :ref:`dotnet-advanced-pe-image-building`. - - -Creating a new .NET assembly ----------------------------- - -AsmResolver also supports creating entire (multi-module) .NET assemblies instead. - -.. code-block:: csharp - - var assembly = new AssemblyDefinition("MyAssembly", new Version(1, 0, 0, 0)); - - -Opening a .NET assembly ------------------------ - -Opening (multi-module) .NET assemblies can be done in a very similar fashion as reading a single module: - -.. code-block:: csharp - - byte[] raw = ... - var assembly = AssemblyDefinition.FromBytes(raw); - -.. code-block:: csharp - - var assembly = AssemblyDefinition.FromFile(@"C:\myfile.exe"); - -.. code-block:: csharp - - IPEFile peFile = ... - var assembly = AssemblyDefinition.FromFile(peFile); - -.. code-block:: csharp - - BinaryStreamReader reader = ... - var assembly = AssemblyDefinition.FromReader(reader); - -.. code-block:: csharp - - IPEImage peImage = ... - var assembly = AssemblyDefinition.FromImage(peImage); - - -Similar to reading module definitions, if you want to read large files (+100MB), consider using memory mapped I/O instead: - -.. code-block:: csharp - - using var service = new MemoryMappedFileService(); - var assembly = AssemblyDefinition.FromFile(service.OpenFile(@"C:\myfile.exe")); - - -Writing a .NET assembly ------------------------ - -Writing a .NET assembly can be done through one of the ``Write`` method overloads. - -.. code-block:: csharp - - assembly.Write(@"C:\myfile.patched.exe"); - -For more advanced options to write .NET assemblies, see :ref:`dotnet-advanced-pe-image-building`. \ No newline at end of file diff --git a/docs/dotnet/bundles.rst b/docs/dotnet/bundles.rst deleted file mode 100644 index 81ef259e5..000000000 --- a/docs/dotnet/bundles.rst +++ /dev/null @@ -1,202 +0,0 @@ -AppHost / SingleFileHost Bundles -================================ - -Since the release of .NET Core 3.1, it is possible to deploy .NET assemblies as a single binary. These files are executables that do not contain a traditional .NET metadata header, and run natively on the underlying operating system via a platform-specific application host bootstrapper. - -AsmResolver supports extracting the embedded files from these types of binaries. Additionally, given the original file or an application host template provided by the .NET SDK, AsmResolver also supports constructing new bundles as well. All relevant code is found in the following namespace: - -.. code-block:: csharp - - using AsmResolver.DotNet.Bundles; - - -Creating Bundles ----------------- - -.NET bundles are represented using the ``BundleManifest`` class. Creating new bundles can be done using any of the constructors: - -.. code-block:: csharp - - var manifest = new BundleManifest(majorVersionNumber: 6); - - -The major version number refers to the file format that should be used when saving the manifest. Below an overview of the values that are recognized by the CLR: - -+----------------------+----------------------------+ -| .NET Version Number | Bundle File Format Version | -+======================+============================+ -| .NET Core 3.1 | 1 | -+----------------------+----------------------------+ -| .NET 5.0 | 2 | -+----------------------+----------------------------+ -| .NET 6.0 | 6 | -+----------------------+----------------------------+ - -To create a new bundle with a specific bundle identifier, use the overloaded constructor - -.. code-block:: csharp - - var manifest = new BundleManifest(6, "MyBundleID"); - - -It is also possible to change the version number as well as the bundle ID later, since these values are exposed as mutable properties ``MajorVersion`` and ``BundleID`` - -.. code-block:: csharp - - manifest.MajorVersion = 6; - manifest.BundleID = manifest.GenerateDeterministicBundleID(); - -.. note:: - - If ``BundleID`` is left unset (``null``), it will be automatically assigned a new one using ``GenerateDeterministicBundleID`` upon writing. - - -Reading Bundles ---------------- - -Reading and extracting existing bundle manifests from an executable can be done by using one of the ``FromXXX`` methods: - -.. code-block:: csharp - - var manifest = BundleManifest.FromFile(@"C:\Path\To\Executable.exe"); - -.. code-block:: csharp - - byte[] contents = ... - var manifest = BundleManifest.FromBytes(contents); - -.. code-block:: csharp - - IDataSource contents = ... - var manifest = BundleManifest.FromDataSource(contents); - - -Similar to the official .NET bundler and extractor, the methods above locate the bundle in the file by looking for a specific signature first. However, official implementations of the application hosting program itself actually do not verify or use this signature in any shape or form. This means that a third party can replace or remove this signature, or write their own implementation of an application host that does not adhere to this standard, and thus throw off static analysis of the file. - -AsmResolver does not provide built-in alternative heuristics for finding the right start address of the bundle header. However, it is possible to implement one yourself and provide the resulting start address in one of the overloads of the ``FromXXX`` methods: - -.. code-block:: csharp - - byte[] contents = ... - ulong bundleAddress = ... - var manifest = BundleManifest.FromBytes(contents, bundleAddress); - -.. code-block:: csharp - - IDataSource contents = ... - ulong bundleAddress = ... - var manifest = BundleManifest.FromDataSource(contents, bundleAddress); - - -Writing Bundles ---------------- - -Constructing new bundled executable files requires a template file that AsmResolver can base the final output on. This is similar how .NET compilers themselves do this as well. By default, the .NET SDK installs template binaries in one of the following directories: - -- ``/sdk//AppHostTemplate`` -- ``/packs/Microsoft.NETCore.App.Host.//runtimes//native`` - -Using this template file, it is then possible to write a new bundled executable file using ``WriteUsingTemplate`` and the ``BundlerParameters::FromTemplate`` method: - -.. code-block:: csharp - - BundleManifest manifest = ... - manifest.WriteUsingTemplate( - @"C:\Path\To\Output\File.exe", - BundlerParameters.FromTemplate( - appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", - appBinaryPath: @"HelloWorld.dll")); - - -Typically on Windows, use an ``apphost.exe`` template if you want to construct a native binary that is framework dependent, and ``singlefilehost.exe`` for a fully self-contained binary. On Linux, use the ``apphost`` and ``singlefilehost`` ELF equivalents. - -For bundle executable files targeting Windows, it may be required to copy over some values from the original PE file into the final bundle executable file. Usually these values include fields from the PE headers (such as the executable's sub-system target) and Win32 resources (such as application icons and version information). AsmResolver can automatically update these headers by specifying a source image to pull this data from in the ``BundlerParameters``: - -.. code-block:: csharp - - BundleManifest manifest = ... - manifest.WriteUsingTemplate( - @"C:\Path\To\Output\File.exe", - BundlerParameters.FromTemplate( - appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", - appBinaryPath: @"HelloWorld.dll", - imagePathToCopyHeadersFrom: @"C:\Path\To\Original\HelloWorld.exe")); - - -If you do not have access to a template file (e.g., if the SDK is not installed) but have another existing PE file that was packaged in a similar fashion, it is then possible to use this file as a template instead by extracting the bundler parameters using the ``BundlerParameters::FromExistingBundle`` method. This is in particularly useful when trying to patch existing AppHost bundles: - -.. code-block:: csharp - - string inputPath = @"C:\Path\To\Bundled\HelloWorld.exe"; - string outputPath = Path.ChangeExtension(inputPath, ".patched.exe"); - - // Read manifest. - var manifest = BundleManifest.FromFile(inputPath); - - /* ... Make changes to manifest and its files ... */ - - // Repackage bundle using existing bundle as template. - manifest.WriteUsingTemplate( - outputPath, - BundlerParameters.FromExistingBundle( - originalFile: inputPath, - appBinaryPath: mainFile.RelativePath)); - - -.. warning:: - - The ``BundlerParameters.FromExistingBundle`` method applies heuristics on the input file to determine the parameters for patching the input file. As heuristics are not perfect, this is not guaranteed to always work. - - -``BundleManifest`` and ``BundlerParameters`` also define overloads of the ``WriteUsingTemplate`` and ``FromTemplate`` / ``FromExistingBundle`` respectively, taking ``byte[]``, ``IDataSource`` or ``IPEImage`` instances instead of file paths. - - -Managing Files --------------- - -Files in a bundle are represented using the ``BundleFile`` class, and are exposed by the ``BundleManifest.Files`` property. Both the class as well as the list itself is fully mutable, and thus can be used to add, remove or modify files in the bundle. - -Creating a new file can be done using the constructors: - -.. code-block:: csharp - - var newFile = new BundleFile( - relativePath: "HelloWorld.dll", - type: BundleFileType.Assembly, - contents: System.IO.File.ReadAllBytes(@"C:\Binaries\HelloWorld.dll")); - - manifest.Files.Add(newFile); - - -It is also possible to iterate over all files and inspect their contents using ``GetData``: - -.. code-block:: csharp - - foreach (var file in manifest.Files) - { - string path = file.RelativePath; - byte[] contents = file.GetData(); - - Console.WriteLine($"Extracting {path}..."); - System.IO.File.WriteAllBytes(path, contents); - } - - -Changing the contents of an existing file can be done using the ``Contents`` property. - -.. code-block:: csharp - - BundleFile file = ... - file.Contents = new DataSegment(new byte[] { 1, 2, 3, 4 }); - - -If the bundle manifest is put into a single-file host template (e.g. ``singlefilehost.exe``), then files can also be compressed or decompressed: - -.. code-block:: csharp - - file.Compress(); - // file.Contents now contains the compressed version of the data and file.IsCompressed = true - - file.Decompress(); - // file.Contents now contains the decompressed version of the data and file.IsCompressed = false - diff --git a/docs/dotnet/cloning.rst b/docs/dotnet/cloning.rst deleted file mode 100644 index 6797896ef..000000000 --- a/docs/dotnet/cloning.rst +++ /dev/null @@ -1,263 +0,0 @@ -Member Cloning -============== - -Processing a .NET module often involves injecting additional code. Even though all models representing .NET metadata and CIL code are mutable, it might be very time-consuming and error-prone to manually import and inject metadata members and/or CIL code into the target module. - -To help developers in injecting existing code into a module, ``AsmResolver.DotNet`` comes with a feature that involves cloning metadata members from one module and copying it over to another. All relevant classes are in the ``AsmResolver.DotNet.Cloning`` namespace: - -.. code-block:: csharp - - using AsmResolver.DotNet.Cloning; - - -The MemberCloner class ----------------------- - -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 = ... - 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 ---------------- - -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``: - -.. code-block:: csharp - - public class Rectangle - { - public Rectangle(Vector2 location, Vector2 size) - { - Location = location; - Size = size; - } - - public Vector2 Location { get; set; } - public Vector2 Size { get; set; } - - public Vector2 GetCenter() => new Vector2(Location.X + Size.X / 2, Location.Y + Size.Y / 2); - } - - public class Vector2 - { - public Vector2(int x, int y) - { - X = x; - Y = y; - } - - public int X { get; set; } - public int Y { get; set; } - } - -The first step in cloning involves loading the source module, and finding the type definitions that correspond to these classes: - -.. code-block:: csharp - - var sourceModule = ModuleDefinition.FromFile(...); - 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 - - var sourceModule = ModuleDefinition.FromFile(typeof(Rectangle).Assembly.Location); - var rectangleType = (TypeDefinition) sourceModule.LookupMember(typeof(Rectangle).MetadataToken); - var vectorType = (TypeDefinition) sourceModule.LookupMember(typeof(Vector2).MetadataToken); - - -We can then use ``MemberCloner.Include`` to include the types in the cloning procedure: - -.. code-block:: csharp - - 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. 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) - .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 ----------------------------- - -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. - -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. - - -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: - -.. 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 is 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 ----------------------------- - -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. -- ``ClonedTopLevelTypes``: A subset of ``ClonedMembers``, containing all cloned top-level types. - -Original members can be mapped to their cloned counterpart, using the ``GetClonedMember`` method: - -.. code-block:: csharp - - var clonedRectangleType = result.GetClonedMember(rectangleType); - -Alternatively, we can get all cloned top-level types. - -.. code-block:: csharp - - var clonedTypes = result.ClonedTopLevelTypes; - -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); - - -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 deleted file mode 100644 index 645be53a0..000000000 --- a/docs/dotnet/dynamic-methods.rst +++ /dev/null @@ -1,63 +0,0 @@ -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/importing.rst b/docs/dotnet/importing.rst deleted file mode 100644 index abf3f420c..000000000 --- a/docs/dotnet/importing.rst +++ /dev/null @@ -1,203 +0,0 @@ -.. _dotnet-reference-importing: - -Reference Importing -=================== - -.NET modules use entries in the TypeRef or MemberRef tables to reference types or members from external assemblies. Importing references into the current module, therefore, form a key role when creating new- or modifying existing .NET modules. When a member is not imported into the current module, a ``MemberNotImportedException`` will be thrown when you are trying to create a PE image or write the module to the disk. - -AsmResolver provides the ``ReferenceImporter`` class that does most of the heavy lifting. Obtaining an instance of ``ReferenceImporter`` can be done in two ways. - -Either instantiate one yourself: - -.. code-block:: csharp - - ModuleDefinition module = ... - var importer = new ReferenceImporter(module); - -Or obtain the default instance that comes with every ``ModuleDefinition`` object. This avoids allocating new reference importers every time. - -.. code-block:: csharp - - ModuleDefinition module = ... - var importer = module.DefaultImporter; - - -The example snippets that will follow in this article assume that there is such a ``ReferenceImporter`` object instantiated using either of these two methods, and is stored in an ``importer`` variable. - - -Importing existing members --------------------------- - -Metadata members from external modules can be imported using the ``ReferenceImporter`` class using one of the following members: - -+---------------------------+------------------------+----------------------+ -| Member type to import | Method to use | Result type | -+===========================+========================+======================+ -| ``IResolutionScope`` | ``ImportScope`` | ``IResolutionScope`` | -+---------------------------+------------------------+----------------------+ -| ``AssemblyReference`` | ``ImportScope`` | ``IResolutionScope`` | -+---------------------------+------------------------+----------------------+ -| ``AssemblyDefinition`` | ``ImportScope`` | ``IResolutionScope`` | -+---------------------------+------------------------+----------------------+ -| ``ModuleReference`` | ``ImportScope`` | ``IResolutionScope`` | -+---------------------------+------------------------+----------------------+ -| ``ITypeDefOrRef`` | ``ImportType`` | ``ITypeDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``TypeDefinition`` | ``ImportType`` | ``ITypeDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``TypeReference`` | ``ImportType`` | ``ITypeDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``TypeSpecification`` | ``ImportType`` | ``ITypeDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``IMethodDefOrRef`` | ``ImportMethod`` | ``IMethodDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``MethodDefinition`` | ``ImportMethod`` | ``IMethodDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``MethodSpecification`` | ``ImportMethod`` | ``IMethodDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``IFieldDescriptor`` | ``ImportField`` | ``IFieldDescriptor`` | -+---------------------------+------------------------+----------------------+ -| ``FieldDefinition`` | ``ImportField`` | ``IFieldDescriptor`` | -+---------------------------+------------------------+----------------------+ - -Below an example of how to import a type definition called ``SomeType``: - -.. code-block:: csharp - - ModuleDefinition externalModule = ModuleDefinition.FromFile(...); - TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType"); - - ITypeDefOrRef importedType = importer.ImportType(typeToImport); - - -These types also implement the ``IImportable`` interface. This means you can also use the ``member.ImportWith`` method instead: - -.. code-block:: csharp - - ModuleDefinition externalModule = ModuleDefinition.FromFile(...); - TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType"); - - ITypeDefOrRef importedType = typeToImport.ImportWith(importer); - - -Importing existing type signatures ----------------------------------- - -Type signatures can also be imported using the ``ReferenceImporter`` class, but these should be imported using the ``ImportTypeSignature`` method instead. - -.. note:: - - If a corlib type signature is imported, the appropriate type from the ``CorLibTypeFactory`` of the target module will be selected, regardless of whether CorLib versions are compatible with each other. - - -Importing using System.Reflection ---------------------------------- - -Types and members can also be imported by passing on an instance of various ``System.Reflection`` classes. - -+---------------------------+------------------------+----------------------+ -| Member type to import | Method to use | Result type | -+===========================+========================+======================+ -| ``Type`` | ``ImportType`` | ``ITypeDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``Type`` | ``ImportTypeSignature``| ``TypeSignature`` | -+---------------------------+------------------------+----------------------+ -| ``MethodBase`` | ``ImportMethod`` | ``IMethodDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``MethodInfo`` | ``ImportMethod`` | ``IMethodDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``ConstructorInfo`` | ``ImportMethod`` | ``IMethodDefOrRef`` | -+---------------------------+------------------------+----------------------+ -| ``FieldInfo`` | ``ImportScope`` | ``MemberReference`` | -+---------------------------+------------------------+----------------------+ - -There is limited support for importing complex types. Types that can be imported through reflection include: - -- Pointer types. -- By-reference types. -- Array types (If an array contains only one dimension, a ``SzArrayTypeSignature`` is returned. Otherwise a ``ArrayTypeSignature`` is created). -- Generic parameters. -- Generic type instantiations. - -Instantiations of generic methods are also supported. - - -Creating new references ------------------------ - -Member references can also be created and imported without having direct access to its member definition or ``System.Reflection`` instance. It is possible to create new instances of ``TypeReference`` and ``MemberReference`` using the constructors, but the preferred way is to use the factory methods that allow for a more fluent syntax. Below is an example of how to create a fully imported reference to ``void System.Console.WriteLine(string)``: - -.. code-block:: csharp - - var factory = module.CorLibTypeFactory; - var importedMethod = factory.CorLibScope - .CreateTypeReference("System", "Console") - .CreateMemberReference("WriteLine", MethodSignature.CreateStatic( - factory.Void, factory.String)) - .ImportWith(importer); - - // importedMethod now references "void System.Console.WriteLine(string)" - -Generic type instantiations can also be created using ``MakeGenericInstanceType``: - -.. code-block:: csharp - - ModuleDefinition module = ... - - var factory = module.CorLibTypeFactory; - var importedMethod = factory.CorLibScope - .CreateTypeReference("System.Collections.Generic", "List`1") - .MakeGenericInstanceType(factory.Int32) - .ToTypeDefOrRef() - .CreateMemberReference("Add", MethodSignature.CreateInstance( - factory.Void, - new GenericParameterSignature(GenericParameterType.Type, 0))) - .ImportWith(importer); - - // importedMethod now references "System.Collections.Generic.List`1.Add(!0)" - - -Similarly, generic method instantiations can be constructed using ``MakeGenericInstanceMethod``: - -.. code-block:: csharp - - ModuleDefinition module = ... - - var factory = module.CorLibTypeFactory; - var importedMethod = factory.CorLibScope - .CreateTypeReference("System", "Array") - .CreateMemberReference("Empty", MethodSignature.CreateStatic( - new GenericParameterSignature(GenericParameterType.Method, 0).MakeSzArrayType(), 1)) - .MakeGenericInstanceMethod(factory.String) - .ImportWith(importer); - - // importedMethod now references "!0[] System.Array.Empty()" - - -.. _dotnet-importer-common-caveats: - -Common Caveats using the Importer ---------------------------------- - -Caching and reuse of instances -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The default implementation of ``ReferenceImporter`` does not maintain a cache. Each call to any of the import methods will result in a new instance of the imported member. The exception to this rule is when the member passed onto the importer is defined in the module the importer is targeting itself, or was already a reference imported by an importer into the target module. In both of these cases, the same instance of this member definition or reference will be returned instead. - -Importing cross-framework versions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``ReferenceImporter`` does not support importing across different versions of the target framework. Members are being imported as-is, and are not automatically adjusted to conform with other versions of a library. - -As a result, trying to import from for example a library part of the .NET Framework into a module targeting .NET Core or vice versa has a high chance of producing an invalid .NET binary that cannot be executed by the runtime. For example, attempting to import a reference to ``[System.Runtime] System.DateTime`` into a module targeting .NET Framework will result in a new reference targeting a .NET Core library (``System.Runtime``) as opposed to the appropriate .NET Framework library (``mscorlib``). - -This is a common mistake when trying to import using metadata provided by ``System.Reflection``. For example, if the host application that uses AsmResolver targets .NET Core but the input file is targeting .NET Framework, then you will run in the exact issue described in the above. - -.. code-block:: csharp - - var reference = importer.ImportType(typeof(DateTime)); - - // `reference` will target `[mscorlib] System.DateTime` when running on .NET Framework, and `[System.Runtime] System.DateTime` when running on .NET Core. - - -Therefore, always make sure you are importing from a .NET module that is compatible with the target .NET module. diff --git a/docs/dotnet/index.rst b/docs/dotnet/index.rst deleted file mode 100644 index 13d22fbde..000000000 --- a/docs/dotnet/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -Overview -======== - -The .NET image layer is the third layer of abstraction of the portable executable (PE) file format. It provides a high-level abstraction of the .NET metadata stored in a PE image, that is similar to APIs like ``System.Reflection``. Its root objects are ``AssemblyDefinition`` and ``ModuleDefinition``, and from there it is possible to dissect the .NET assembly hierarchically. - -In short, this means the following: - -* Assemblies define modules, -* Modules define types, resources and external references, -* Types define members such as methods, fields, properties and events, -* Methods include method bodies, -* ... and so on. - -The third layer of abstraction is the highest level of abstraction for a .NET assembly that AsmResolver provides. All objects exposed by this layer are completely mutable and can be serialized back to a ``IPEImage`` from the second layer, to a ``PEFile`` from the first layer, or to the disk. diff --git a/docs/dotnet/managed-method-bodies.rst b/docs/dotnet/managed-method-bodies.rst deleted file mode 100644 index c3d7ce70d..000000000 --- a/docs/dotnet/managed-method-bodies.rst +++ /dev/null @@ -1,310 +0,0 @@ -.. _dotnet-cil-method-bodies: - -CIL Method Bodies -================= - -The relevant models in this document can be found in the following namespaces: - -.. code-block:: csharp - - using AsmResolver.PE.DotNet.Cil; // Raw models for the CIL language. - using AsmResolver.DotNet.Code.Cil; // High level and easy to use models related to CIL. - - -The CilMethodBody class ------------------------ - -The ``MethodDefinition`` class defines a property called ``CilMethodBody``, which exposes the managed implementation of the method, written in the Common Intermediate Language, or CIL for short. - -Each ``CilMethodBody`` is assigned to exactly one ``MethodDefinition``. Upon instantiation of such a method body, it is therefore required to specify the owner of the body: - -.. code-block:: csharp - - MethodDefinition method = ... - - CilMethodBody body = new CilMethodBody(method); - method.CilMethodBody = body; - - -The ``CilMethodBody`` class consists of the following basic building blocks: - -- ``Instructions``: The instructions to be executed. -- ``LocalVariables``: The local variables defined by the method body. -- ``ExceptionHandlers``: A collection of regions protected by an exception handler. - -Basic structure of CIL instructions ------------------------------------ - -Instructions that are assembled into the method body are automatically disassembled and put in a mutable collection of ``CilInstruction``, accessible by the ``Instructions`` property. - -.. code-block:: csharp - - var instructions = body.Instructions; - -The ``CilInstruction`` class defines three basic properties: - -- ``Offset``: The offset of the instruction, relative to the start of the code stream. -- ``OpCode``: The operation the instruction performs. -- ``Operand``: The operand of the instruction. - -By default, depending on the value of ``OpCode.OperandType``, ``Operand`` contains (and always should contain) one of the following: - -+----------------------------------------+----------------------------------------------+ -| OpCode.OperandType | Type of Operand | -+========================================+==============================================+ -| ``CilOperandType.InlineNone`` | N/A (is always ``null``) | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.ShortInlineI`` | ``sbyte`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineI`` | ``int`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineI8`` | ``long`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.ShortInlineR`` | ``float`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineR`` | ``double`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineString`` | ``string`` or ``MetadataToken`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineBrTarget`` | ``ICilLabel`` or ``int`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.ShortInlineBrTarget`` | ``ICilLabel`` or ``sbyte`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineSwitch`` | ``IList`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.ShortInlineVar`` | ``CilLocalVariable`` or ``byte`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineVar`` | ``CilLocalVariable`` or ``ushort`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.ShortInlineArgument`` | ``Parameter`` or ``byte`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineArgument`` | ``Parameter`` or ``ushort`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineField`` | ``IFieldDescriptor`` or ``MetadataToken`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineMethod`` | ``IMethodDescriptor`` or ``MetadataToken`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineSig`` | ``StandAloneSignature`` or ``MetadataToken`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineTok`` | ``IMetadataMember`` or ``MetadataToken`` | -+----------------------------------------+----------------------------------------------+ -| ``CilOperandType.InlineType`` | ``ITypeDefOrRef`` or ``MetadataToken`` | -+----------------------------------------+----------------------------------------------+ - -.. warning:: - - Providing an incorrect operand type will result in the CIL assembler to fail assembling the method body upon writing the module to the disk. - -Creating a new instruction can be done using one of the constructors, together with the ``CilOpCodes`` static class: - -.. code-block:: csharp - - body.Instructions.AddRange(new[] - { - new CilInstruction(CilOpCodes.Ldstr, "Hello, World!"), - new CilInstruction(CilOpCodes.Ret), - }); - -However, the preferred way of adding instructions to add or insert new instructions is to use one of the ``Add`` or ``Insert`` overloads that directly take an opcode and operand. This is because it avoids an allocation of an array, and the overloads perform immediate validation on the created instruction. - -.. code-block:: csharp - - var instructions = body.Instructions; - instructions.Add(CilOpCodes.Ldstr, "Hello, World!"); - instructions.Add(CilOpCodes.Ret); - - -Pushing 32-bit integer constants onto the stack ------------------------------------------------ - -In CIL, pushing integer constants onto the stack is done using one of the ``ldc.i4`` instruction variants. - -The recommended way to create such an instruction is not to use the constructor, but instead use the ``CilInstruction.CreateLdcI4(int)`` method instead. This automatically selects the smallest possible opcode possible and sets the operand accordingly: - -.. code-block:: csharp - - CilInstruction push1 = CilInstruction.CreateLdcI4(1); // Returns "ldc.i4.1" macro - CilInstruction pushShort = CilInstruction.CreateLdcI4(123); // Returns "ldc.i4.s 123" macro - CilInstruction pushLarge = CilInstruction.CreateLdcI4(12345678); // Returns "ldc.i4 12345678" - -If we want to get the pushed value, we can use the ``CilInstruction.GetLdcI4Constant()`` method. This method works on any of the ``ldc.i4`` variants, including all the macro opcodes that do not explicitly define an operand such as ``ldc.i4.1``. - - -Branching Instructions ----------------------- - -Branch instructions are instructions that (might) transfer control to another part of the method body. To reference the instruction to jump to (the branch target), ``ICilLabel`` is used. The easiest way to create such a label is to use the ``CreateLabel()`` function on the instruction to reference: - -.. code-block:: csharp - - CilInstruction targetInstruction = ... - ICilLabel label = targetInstruction.CreateLabel(); - - instructions.Add(CilOpCodes.Br, label); - -Alternatively, when using the ``Add`` or ``Insert`` overloads, it is possible to use the return value of these overloads. - -.. code-block:: csharp - - var instructions = body.Instructions; - var label = new CilInstructionLabel(); - - instructions.Add(CilOpCodes.Br, label); - /* ... */ - label.Instruction = instruction.Add(CilOpCodes.Ret); - - -The ``switch`` operation uses a ``IList`` instead. - -.. note:: - - When a branching instruction contains a ``null`` label or a label that references an instruction that is not present in the method body, AsmResolver will by default report an exception upon serializing the code stream. This can be disabled by setting ``VerifyLabelsOnBuild`` to ``false``. - - -Finding instructions by offset ------------------------------- - -Instructions stored in a method body are indexed not by offset, but by order of occurrence. If it is required to find an instruction by offset, it is possible to use the ``Instructions.GetByOffset(int)`` method, which performs a binary search (O(log(n))) and is faster than a linear search (O(n)) such as a for loop or using a construction like ``.First(i => i.Offset == offset)`` provided by ``System.Linq``. - -For ``GetByOffset`` to work, it is required that all offsets in the instruction collection are up to date. Recalculating all offsets within an instruction collection can be done through ``Instructions.CalculateOffsets()``. - -.. code-block:: csharp - - // Calculate all offsets once ... - body.Instructions.CalculateOffsets(); - - // Look up multiple times. - var instruction1 = body.Instructions.GetByOffset(0x0012); - var instruction2 = body.Instructions.GetByOffset(0x0020); - - // Find the index of an instruction. - int index = body.Instructions.GetIndexByOffset(0x0012); - instruction1 = body.Instructions[index]; - - -Referencing members -------------------- - -As specified by the table above, operations such as a ``call`` require a member as operand. - -It is important that the member referenced in the operand of such an instruction is imported in the module. This can be done using the ``ReferenceImporter`` class. - -Below an example on how to use the ``ReferenceImporter`` to emit a call to ``Console::WriteLine(string)`` using reflection: - -.. code-block:: csharp - - var importer = new ReferenceImporter(targetModule); - var writeLine = importer.ImportMethod(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) } ); - - body.Instructions.Add(new CilInstruction(CilOpCodes.Ldstr, "Hello, world!")); - body.Instructions.Add(new CilInstruction(CilOpCodes.Call, writeLine)); - - -More information on the capabilities and limitations of the ``ReferenceImporter`` can be found in :ref:`dotnet-reference-importing`. - - -Expanding and optimising macros -------------------------------- - -CIL defines a couple of macro operations that do the same as their full counterpart, but require less space to be encoded. For example, the ``ldc.i4.1`` instruction is a macro for ``ldc.i4 1``, and requires 1 byte instead of 5 bytes to do the same thing. - -AsmResolver is able to expand macros to their larger sized counterparts and back using the ``Instructions.ExpandMacros()`` and ``Instructions.OptimizeMacros()``. - -.. code-block:: csharp - - var instruction = new CilInstruction(CilOpCodes.Ldc_I4, 1); - body.Instructions.Add(instruction); - - body.Instructions.OptimizeMacros(); - - // instruction is now optimized to "ldc.i4.1". - -.. code-block:: csharp - - var instruction = new CilInstruction(CilOpCodes.Ldc_I4_1); - body.Instructions.Add(instruction); - - body.Instructions.ExpandMacros(); - - // instruction is now expanded to "ldc.i4 1". - - -Pretty printing CIL instructions --------------------------------- - -Instructions can be formatted using e.g. an instance of the ``CilInstructionFormatter``: - -.. code-block:: csharp - - var formatter = new CilInstructionFormatter(); - foreach (CilInstruction instruction in body.Instructions) - Console.WriteLine(formatter.FormatInstruction(instruction)); - - -Patching CIL instructions -------------------------- - -Instructions can be added or removed using the ``Add``, ``Insert``, ``Remove`` and ``RemoveAt`` methods: - -.. code-block:: csharp - - body.Instructions.Add(CilOpCodes.Ldstr, "Hello, world!"); - body.Instructions.Insert(i, CilOpCodes.Ldc_I4, 1234); - body.Instructions.RemoveAt(i); - -... or by using the indexer to replace existing instructions: - -.. code-block:: csharp - - body.Instructions[i] = new CilInstruction(CilOpCodes.Ret); - -Removing or replacing instructions may not always be favourable. The original ``CilInstruction`` object might be used as a reference for a branch target or exception handler boundary. Removing or replacing these ``CilInstruction`` objects would therefore break these kinds of references, rendering the body invalid. Rather than updating all references manually, it may therefore be wiser to reuse the ``CilInstruction`` object and simply modify the ``OpCode`` and ``Operand`` properties instead: - -.. code-block:: csharp - - body.Instructions[i].OpCode = CilOpCodes.Ldc_I4; - body.Instructions[i].Operand = 1234; - -AsmResolver provides a helper function ``ReplaceWith`` that shortens the code into a single line: - -.. code-block:: csharp - - body.Instructions[i].ReplaceWith(CilOpCodes.Ldc_I4, 1234); - -Since it is very common to replace instructions with a `nop`, AsmResolver also defines a special ``ReplaceWithNop`` helper function: - -.. code-block:: csharp - - body.Instructions[i].ReplaceWithNop(); - - -Exception handlers ------------------- - -Exception handlers are regions in the method body that are protected from exceptions. In AsmResolver, they are represented by the ``CilExceptionHandler`` class, and define the following properties: - -- ``HandlerType``: The type of handler. -- ``TryStart``: The label indicating the start of the protected region. -- ``TryEnd``: The label indicating the end of the protected region. This label is exclusive, i.e. it marks the first instruction that is not included in the region. -- ``HandlerStart``: The label indicating the start of the handler region. -- ``HandlerEnd``: The label indicating the end of the handler region. This label is exclusive, i.e. it marks the first instruction that is not included in the region. -- ``FilterStart``: The label indicating the start of the filter expression, if available. -- ``ExceptionType``: The type of exceptions that are caught by the handler. - -Depending on the value of ``HandlerType``, either ``FilterStart`` or ``ExceptionType``, or neither has a value. - -.. note:: - - Similar to branch instructions, when an exception handler contains a ``null`` label or a label that references an instruction that is not present in the method body, AsmResolver will report an exception upon serializing the code stream. This can be disabled by setting ``VerifyLabelsOnBuild`` to ``false``. - - -Maximum stack depth -------------------- - -CIL method bodies work with a stack, and the stack has a pre-defined size. This pre-defined size is defined by the ``MaxStack`` property. - -The max stack can be computed by using the ``ComputeMaxStack`` method. By default, AsmResolver automatically calculates the maximum stack depth of a method body upon writing the module to the disk. If you want to override this behaviour, set ``ComputeMaxStackOnBuild`` to ``false``. - -.. note:: - - If a ``StackImbalanceException`` is thrown upon writing the module to the disk, or upon calling ``ComputeMaxStack``, it means that not all execution paths in the provided CIL code push or pop the expected amount of values. It is a good indication that the provided CIL code is invalid. diff --git a/docs/dotnet/managed-resources.rst b/docs/dotnet/managed-resources.rst deleted file mode 100644 index 5a66add04..000000000 --- a/docs/dotnet/managed-resources.rst +++ /dev/null @@ -1,239 +0,0 @@ -Managed Resources -================= - -.NET modules may define one or more resource files. Similar to Win32 resources, these are files that contain additional data, such as images, strings or audio files, that are used by the module at run time. - -Manifest Resources ------------------- - -AsmResolver models managed resources using the ``ManifestResource`` class, and they are exposed by the ``ModuleDefinition.Resources`` property. Below an example snippet that prints the names of all resources in a given module: - -.. code-block:: csharp - - var module = ModuleDefinition.FromFile(...); - foreach (var resource in module.Resources) - Console.WriteLine(resource.Name); - - -A ``ManifestResource`` can either be embedded in the module itself, or present in an external assembly or file. When it is embedded, the contents of the file can be accessed using the ``EmbeddedDataSegment`` property. This is a mutable property, so it is also possible to assign new data to the resource this way. - -.. code-block:: csharp - - ManifestResource resource = ... - if (resource.IsEmbedded) - { - // Get data segment of the resource. - var oldData = resource.EmbeddedDataSegment; - - // Assign new data to the resource. - var newData = new DataSegment(new byte[] { 1, 2, 3, 4}); - resource.EmbeddedDataSegment = newData; - } - - -The ``ManifestResource`` class also defines a convenience ``GetData`` method, for quickly obtaining the data stored in the resource as a ``byte[]``: - -.. code-block:: csharp - - ManifestResource resource = ... - if (resource.IsEmbedded) - { - byte[] data = resource.GetData(); - // ... - } - - -Alternatively, you can use the ``TryGetReader`` method to immediately instantiate a ``BinaryStreamReader`` for the data. This can be useful if you want to parse the contents of the resource file later. - -.. code-block:: csharp - - ManifestResource resource = ... - if (resource.TryGetReader(out var reader) - { - // ... - } - - -If the resource is not embedded, the ``Implementation`` property will indicate in which file the resource can be found, and ``Offset`` will indicate where in this file the data starts. - -.. code-block:: csharp - - ManifestResource resource = ... - switch (resource.Implementation) - { - case FileReference fileRef: - // Resource is stored in another file. - string name = fileRef.Name; - uint offset = resource.Offset; - ... - break; - - case AssemblyReference assemblyRef: - // Resource is stored in another assembly. - var assembly = assemblyRef.Resolve(); - var actualResource = assembly.ManifestModule.Resources.First(r => r.Name == resource.Name); - ... - break; - - case null: - // Resource is embedded. - ... - break - } - - -Resource Sets -------------- - -Many .NET applications (mainly Windows Forms apps) make use of manifest resources to store *resource sets*. These are resources that have the ``.resources`` file extension, and combine multiple smaller resources (often localized strings or images) into one manifest resource file. - -AsmResolver supports parsing and building new resource sets using the ``ResourceSet`` class. This class is defined in the ``AsmResolver.DotNet.Resources`` namespace: - -.. code-block:: csharp - - using AsmResolver.DotNet.Resources; - - -.. warning:: - - Adding this ``using`` statement might introduce a name resolution conflict with the (original) ``ResourceSet`` class defined in ``System.Resources``. Generally speaking, you will not need both classes at the same time, as ``ResourceSet`` from AsmResolver is meant to replace the one from ``System.Resources``. However, if you do need to use both classes in the same file, make sure you are using the correct one for your use-case. This can for example be achieved by specifying the fully qualified name (e.g. ``System.Resources.ResourceSet``), or by introducing an alias (e.g. ``using SystemResourceSet = System.Resources.ResourceSet;``) instead. - - -Creating new Resource Sets -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Creating new sets can be done using the constructors of ``ResourceSet``. - -.. code-block:: csharp - - var set = new ResourceSet(); - - -By default, the parameterless constructor will create a resource set with a header that references the ``System.Resources.ResourceReader`` and ``System.Resources.RuntimeResourceSet`` types, both from ``mscorlib`` version ``4.0.0.0``. This can be customized if needed, by using another constructor overload that takes a ``ResourceManagerHeader`` instance instead: - -.. code-block:: csharp - - var set = new ResourceSet(ResourceManagerHeader.Deserializing_v4_0_0_0); - - -Alternatively, you can change the header using the ``ResourceSet.ManagerHeader`` property: - -.. code-block:: csharp - - var set = new ResourceSet(); - set.ManagerHeader = ResourceManagerHeader.Deserializing_v4_0_0_0; - - -Reading existing Resource Sets -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Reading existing resource sets can be done using the ``ResourceSet.FromReader`` method: - -.. code-block:: csharp - - ManifestResource resource = ... - if (resource.TryGetReader(out var reader) - { - var set = ResourceSet.FromReader(reader); - // ... - } - - -By default, AsmResolver will read and deserialize entries in a resource set. However, to prevent arbitrary code execution, it will not interpret the data of each entry that is of a non-intrinsic resource type. For these types of entries, AsmResolver will expose the raw data as a ``byte[]`` instead. If you want to change this behavior, you can provide a custom instance of ``IResourceDataSerializer`` or extend the default serializer so that it supports additional resource types. - - -.. code-block:: csharp - - public class MyResourceDataSerializer : DefaultResourceDataSerializer - { - /// - public override object? Deserialize(ref BinaryStreamReader reader, ResourceType type) - { - // ... - } - } - - ManifestResource resource = ... - if (resource.TryGetReader(out var reader) - { - var set = ResourceSet.FromReader(reader, new MyResourceDataSerializer()); - // ... - } - - -Accessing Resource Set Entries -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``ResourceSet`` class is a mutable list of ``ResourceSetEntry``, which includes the name, the type of the resource and the deserialized data: - -.. code-block:: csharp - - foreach (var entry in set) - { - Console.WriteLine("Name: " + entry.Name); - Console.WriteLine("Type: " + entry.Type.FullName); - Console.WriteLine("Data: " + entry.Data); - } - - -New items can be created using any of the constructors. - -.. code-block:: csharp - - var stringEntry = new ResourceSetEntry("MyString", ResourceTypeCode.String, "Hello, world!"); - set.Add(stringEntry); - - var intEntry = new ResourceSetEntry("MyInt", ResourceTypeCode.Int32, 1234); - set.Add(intEntry); - - -AsmResolver also supports reading and adding resource elements that are of a user-defined type: - -.. code-block:: csharp - - var pointType = new UserDefinedResourceType( - "System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); - - var serializedContents = new byte[] - { - 0x03, 0x06, 0x31, 0x32, 0x2C, 0x20, 0x33, 0x34 // "12, 34" - }; - - var entry = new ResourceSetEntry("MyLocation", type, serializedContents); - set.Add(entry); - - -.. note:: - - When using user-defined types, some implementations of the CLR will require a special resource reader type (such as ``System.Resources.Extensions.DeserializingResourceReader``) to be referenced in the manager header of the resource set. Therefore, make sure you have the right manager header provided in the ``ResourceSet`` that defines such a compatible reader type. - - -Writing Resource Sets -~~~~~~~~~~~~~~~~~~~~~ - -Serializing resource sets can be done using the ``ResourceSet.Write`` method. - -.. code-block:: csharp - - using var stream = new MemoryStream(); - var writer = new BinaryStreamWriter(stream); - set.Write(writer); - - - -By default, AsmResolver will serialize entries in a resource set using a default serializer. However, to prevent arbitrary code execution, it will not attempt to serialize objects that are of a non-intrinsic resource type. The default serializer expects a ``byte[]`` for user-defined resource types. If you want to change this behavior, you can provide a custom instance of ``IResourceDataSerializer`` or extend the default serializer so that it supports additional resource types. - -.. code-block:: csharp - - public class MyResourceDataSerializer : DefaultResourceDataSerializer - { - /// - public override void Serialize(IBinaryStreamWriter writer, ResourceType type, object? value) - { - // ... - } - } - - using var stream = new MemoryStream(); - var writer = new BinaryStreamWriter(stream); - set.Write(writer, new MyResourceDataSerializer()); \ No newline at end of file diff --git a/docs/dotnet/member-tree.rst b/docs/dotnet/member-tree.rst deleted file mode 100644 index ad6d4f6a8..000000000 --- a/docs/dotnet/member-tree.rst +++ /dev/null @@ -1,115 +0,0 @@ -The Member Tree -=============== - -Assemblies and modules ----------------------- - -The root of every .NET assembly is represented by the ``AssemblyDefinition`` class. This class exposes basic information such as name, version and public key token, but also a collection of all modules that are defined in the assembly. Modules are represented by the ``ModuleDefinition`` class. - -Below an example that enumerates all modules defined in an assembly. - -.. code-block:: csharp - - var assembly = AssemblyDefinition.FromFile(...); - foreach (var module in assembly.Modules) - Console.WriteLine(module.Name); - -Most .NET assemblies only have one module. This main module is also known as the manifest module, and can be accessed directly through the ``AssemblyDefinition.ManifestModule`` property. - - -Obtaining types in a module ---------------------------- - -Types are represented by the ``TypeDefinition`` class. To get the types defined in a module, use the ``ModuleDefinition.TopLevelTypes`` property. A top level types is any non-nested type. Nested types are exposed through the ``TypeDefinition.NestedTypes``. Alternatively, to get all types, including nested types, it is possible to call the ``ModuleDefinition.GetAllTypes`` method instead. - -Below is an example program that iterates through all types recursively and prints them: - -.. code-block:: csharp - - public const int IndentationWidth = 3; - - private static void Main(string[] args) - { - var module = ModuleDefinition.FromFile(...); - DumpTypes(module.TopLevelTypes); - } - - private static void DumpTypes(IEnumerable types, int indentationLevel = 0) - { - string indentation = new string(' ', indentationLevel * IndentationWidth); - foreach (var type in types) - { - // 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 ----------------------------- - -The ``TypeDefinition`` class exposes collections of methods and fields that the type defines: - -.. code-block:: csharp - - foreach (var method in type.Methods) - Console.WriteLine("{0} : {1:X8}", method.Name, method.MetadataToken.ToInt32()); - - -.. code-block:: csharp - - foreach (var field in type.Fields) - Console.WriteLine("{0} : {1:X8}", field.Name, field.MetadataToken.ToInt32()); - -Methods and fields have a ``Signature`` property, that contain the return and parameter types, or the field type respectively. - -.. code-block:: csharp - - MethodDefinition method = ... - Console.WriteLine("Return type: " + method.Signature.ReturnType); - Console.WriteLine("Parameter types: " + string.Join(", ", method.Signature.ParameterTypes)); - - -.. code-block:: csharp - - FieldDefinition field = ... - 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: - -.. code-block:: csharp - - foreach (var parameter in method.Parameters) - Console.WriteLine($"{parameter.Name} : {parameter.ParameterType}"); - - -Obtaining properties and events -------------------------------- - -Obtaining properties and events is similar to obtaining methods and fields; ``TypeDefinition`` exposes them in a list as well: - -.. code-block:: csharp - - foreach (var @event in type.Events) - 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()); - - -Properties and events have methods associated to them. These are accessible through the ``Semantics`` property: - -.. code-block:: csharp - - foreach (MethodSemantics semantic in property.Semantics) - { - Console.WriteLine("{0} {1} : {2:X8}", semantic.Attributes, semantic.Method.Name, - semantic.MetadataToken.ToInt32()); - } diff --git a/docs/dotnet/token-allocation.rst b/docs/dotnet/token-allocation.rst deleted file mode 100644 index 602c38af9..000000000 --- a/docs/dotnet/token-allocation.rst +++ /dev/null @@ -1,30 +0,0 @@ -Metadata Token Allocation -========================= - -A lot of models in a .NET module are assigned a unique metadata token. This token can be accessed through the ``IMetadataMember.MetadataToken`` property. The exception to this rule is newly created metadata members. These newly created members are assigned the zero token (a token with RID = 0). Upon building a module, these tokens will be replaced with their actual tokens. - -Custom Token Allocation ------------------------ - -Some use-cases of AsmResolver will depend on the knowledge of tokens of newly created members before serializing the module. Therefore, AsmResolver provides the ``TokenAllocator`` class, which allows for assigning new tokens to members preemptively. If a module is then written to the disk with the ``MetadataFlags.PreserveTableIndices`` flags set (see Advanced PE Image Building for more information on how that is done), this token will be preserved in the final image. - -The token allocator for a particular module can be accessed through the ``ModuleDefinition.TokenAllocator`` property: - -.. code-block:: csharp - - var allocator = module.TokenAllocator; - -Using the allocator, it is possible to assign metadata tokens to newly created members. This is done using the ``AssignNextAvailableToken`` method: - -.. code-block:: csharp - - var field = new FieldDefinition(...); - someType.Fields.Add(field); - - allocator.AssignNextAvailableToken(field); - - // field.MetadataToken is now non-zero. - -.. warning:: - - Only members with a zero Metadata Token can be assigned a new metadata token. If a metadata member with a non-zero MetadataToken was passed as an argument, this method will throw an ``ArgumentException``. \ No newline at end of file diff --git a/docs/dotnet/type-memory-layout.rst b/docs/dotnet/type-memory-layout.rst deleted file mode 100644 index 182366904..000000000 --- a/docs/dotnet/type-memory-layout.rst +++ /dev/null @@ -1,127 +0,0 @@ -Type Memory Layout -================== - -Sometimes it is useful to know details about the memory layout of a type at runtime. Knowing the memory layout of a type can help in various processes, including: - -- Getting the size of a type at runtime. - -- Calculating field pointer offsets within a type. - -AsmResolver provides an API to statically infer information about the memory layout of any given blittable type. It supports structures marked as ``SequentialLayout`` and ``ExplicitLayout``, as well as field alignments and custom field offsets. - -To get access to the API, you must include the following namespace: - -.. code-block:: csharp - - using AsmResolver.DotNet.Memory; - -Obtaining the type layout -------------------------- - -To get the memory layout of any ``ITypeDescriptor``, use the following extension method: - -.. code-block:: csharp - - ITypeDescriptor type = ... - TypeMemoryLayout typeLayout = type.GetImpliedMemoryLayout(is32Bit: false); - -.. warning:: - - Only value types that are marked with the ``SequentialLayout`` or ``ExplicitLayout`` structure layout are fully supported. If ``AutoLayout`` was provided, a sequential layout is assumed. This might not be the case for all implementations of the CLR. - - -.. warning:: - - If the type contains a cyclic dependency (e.g. a field with field type equal to its enclosing class), this method will throw an instance of the ``CyclicStructureException`` class. - - -The size of a type --------------------------- - -After the memory layout is inferred, you can query the ``Size`` property to obtain the total size in bytes of the type. - -.. code-block:: csharp - - uint size = typeLayout.Size; - - -Getting field offsets ---------------------- - -The ``TypeMemoryLayout`` provides an indexer property that takes an instance of ``FieldDefinition``, and returns an instance of ``FieldMemoryLayout``. - -.. code-block:: csharp - - FieldDefinition field = ... - FieldMemoryLayout fieldLayout = typeLayout[field]; - -This class contains the offset of the queried field within the type: - -.. code-block:: csharp - - uint offset = fieldLayout.Offset; - -It also provides another instance of ``TypeMemoryLayout`` to get the layout of the contents of the field: - -.. code-block:: csharp - - TypeMemoryLayout contentsLayout = fieldLayout.ContentsLayout; - -This can be used to recursively find fields and their offsets. - -.. note:: - - A ``TypeMemoryLayout`` describing the layout of type ``T`` might be different from a ``TypeMemoryLayout`` that was associated to a field, even if this field has field type ``T`` as well. This is due to the fact that the CLR might layout nested fields differently when a structure defines a field with a compound field type. - - -Getting fields by offset ------------------------- - -It is also possible to turn an offset (relative to the start of the type) to the field definition that is stored at that offset. This is done by using the ``TryGetFieldAtOffset`` method. - -.. code-block:: csharp - - uint offset = ... - if (typeLayout.TryGetFieldAtOffset(offset, out var fieldLayout)) - { - // There is a field defined at this offset. - } - -Sometimes, offsets within a structure refer to a field within a nested field. For example, consider the following sample code: - -.. code-block:: csharp - - [StructLayout(LayoutKind.Sequential, Size = 17)] - public struct Struct1 - { - public int Dummy1; - } - - [StructLayout(LayoutKind.Sequential, Size = 23, Pack = 2)] - public struct Struct2 - { - public Struct1 Nest1; - } - - [StructLayout(LayoutKind.Sequential, Size = 87, Pack = 64)] - public struct Struct3 - { - public Struct1 Nest1; - - public Struct2 Nest2; - } - -To get a collection of fields to access to reach a certain offset within the type, use the ``TryGetFieldPath`` method. This method will return ``true`` if the offset refers to the beginning of a field, and ``false`` otherwise. - -.. code-block:: csharp - - var struct3Definition = (TypeDefinition) Module.LookupMember( - typeof(Struct3).MetadataToken); - var struct3Layout = struct3Definition.GetImpliedMemoryLayout(false); - - uint offset = 20; - bool isStartOfField = layout.TryGetFieldPath(offset, out var path); - - // This results in: - // - isStartOfField: true. - // - path: {Struct3::Nest2, Struct2::Nest1, Struct1::Dummy1}. \ No newline at end of file diff --git a/docs/dotnet/type-signatures.rst b/docs/dotnet/type-signatures.rst deleted file mode 100644 index 8803693cd..000000000 --- a/docs/dotnet/type-signatures.rst +++ /dev/null @@ -1,288 +0,0 @@ -Type Signatures -=============== - -Type signatures represent references to types within a blob signature. They are not directly associated with a metadata token, but can reference types defined in one of the metadata tables. - -All relevant classes in this document can be found in the following namespaces: - -.. code-block:: csharp - - using AsmResolver.DotNet.Signatures; - using AsmResolver.DotNet.Signatures.Types; - - -Overview --------- - -Basic leaf type signatures: - -+----------------------------------+----------------------------------------------------------------------+ -| Type signature name | Example | -+==================================+======================================================================+ -| ``CorLibTypeSignature`` | ``int32`` (``System.Int32``) | -+----------------------------------+----------------------------------------------------------------------+ -| ``TypeDefOrRefSignature`` | ``System.IO.Stream``, ``System.Drawing.Point`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``GenericInstanceTypeSignature`` | ``System.Collections.Generic.IList`1`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``FunctionPointerTypeSignature`` | ``method void *(int32, int64)`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``GenericParameterSignature`` | ``!0``, ``!!0`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``SentinelTypeSignature`` | (Used as a delimeter for vararg method signatures) | -+----------------------------------+----------------------------------------------------------------------+ - -Decorator type signatures: - -+----------------------------------+----------------------------------------------------------------------+ -| Type signature name | Example | -+==================================+======================================================================+ -| ``SzArrayTypeSignature`` | ``System.Int32[]`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``ArrayTypeSignature`` | ``System.Int32[0.., 0..]`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``ByReferenceTypeSignature`` | ``System.Int32&`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``PointerTypeSignature`` | ``System.Int32*`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``CustomModifierTypeSignature`` | ``System.Int32 modreq (System.Runtime.CompilerServices.IsVolatile)`` | -+----------------------------------+----------------------------------------------------------------------+ -| ``BoxedTypeSignature`` | (Boxes a value type signature) | -+----------------------------------+----------------------------------------------------------------------+ -| ``PinnedTypeSignature`` | (Pins the value of a local variable in memory) | -+----------------------------------+----------------------------------------------------------------------+ - - -Basic Element Types -------------------- - -The CLR defines a set of primitive types (such as ``System.Int32``, ``System.Object``) as basic element types that can be referenced using a single byte, rather than the fully qualified name. These are represented using the ``CorLibTypeSignature`` class. - -Every ``ModuleDefinition`` defines a property called ``CorLibTypeFactory``, which exposes reusable instances of all corlib type signatures: - -.. code-block:: csharp - - ModuleDefinition module = ... - TypeSignature int32Type = module.CorLibTypeFactory.Int32; - -Corlib type signatures can also be looked up by their element type, by their full name, or by converting a type reference to a corlib type signature. - -.. code-block:: csharp - - int32Type = module.CorLibTypeFactory.FromElementType(ElementType.I4); - int32Type = module.CorLibTypeFactory.FromName("System", "Int32"); - - var int32TypeRef = new TypeReference(corlibScope, "System", "Int32"); - int32Type = module.CorLibTypeFactory.FromType(int32TypeRef); - -If an invalid element type, name or type descriptor is passed on, these methods return ``null``. - - -Class and Struct Types ----------------------- - -The ``TypeDefOrRefSignature`` class is used to reference types in either the ``TypeDef`` or ``TypeRef`` (and sometimes ``TypeSpec``) metadata table. - -.. code-block:: csharp - - TypeReference streamTypeRef = new TypeReference(corlibScope, "System.IO", "Stream"); - TypeSignature streamTypeSig = new TypeDefOrRefSignature(streamTypeRef); - - -Alternatively, ``CreateTypeReference`` can be used on any ``IResolutionScope``: - -.. code-block:: csharp - - var streamTypeSig = corlibScope.CreateTypeReference("System.IO", "Stream"); - - -.. warning:: - - While it is technically possible to reference a basic type such as ``System.Int32`` as a ``TypeDefOrRefSignature``, it renders the .NET module invalid by most implementations of the CLR. Always use the ``CorLibTypeSignature`` to reference basic types within your blob signatures. - - -Generic Instance Types ----------------------- - -The ``GenericInstanceTypeSignature`` class is used to instantiate generic types with type arguments: - -.. code-block:: csharp - - var listTypeRef = new TypeReference(corlibScope, "System.Collections.Generic", "List`1"); - - var listOfString = new GenericInstanceTypeSignature(listTypeRef, - isValueType: false, - typeArguments: new[] { module.CorLibTypeFactory.String }); - - // listOfString now contains a reference to List. - - -Alternatively, a generic instance can also be generated via the ``MakeGenericType`` fluent syntax method: - -.. code-block:: csharp - - var listOfString = corlibScope - .CreateTypeReference("System.Collections.Generic", "List`1") - .MakeGenericInstanceType(module.CorLibTypeFactory.String); - - // listOfString now contains a reference to List. - - -Function Pointer Types ----------------------- - -Function pointer signatures are strongly-typed pointer types used to describe addresses to functions or methods. In AsmResolver, they are represented using a ``MethodSignature``: - -.. code-block:: csharp - - var factory = module.CorLibTypeFactory; - var signature = MethodSignature.CreateStatic( - factory.Void, - factory.Int32, - factory.Int32); - - var type = new FunctionPointerTypeSignature(signature); - - // type now contains a reference to `method void *(int32, int32)`. - - -Alternatively, a function pointer signature can also be generated via the ``MakeFunctionPointerType`` fluent syntax method: - -.. code-block:: csharp - - var factory = module.CorLibTypeFactory; - var type = MethodSignature.CreateStatic( - factory.Void, - factory.Int32, - factory.Int32) - .MakeFunctionPointerType(); - - // type now contains a reference to `method void *(int32, int32)`. - - - -Shortcuts ---------- - -To quickly transform any ``ITypeDescriptor`` into a ``TypeSignature``, it is possible to use the ``.ToTypeSignature()`` method on any ``ITypeDescriptor``. For ``TypeReference`` s, this will also check whether the object is referencing a basic type and return the appropriate ``CorLibTypeSignature`` instead. - -.. code-block:: csharp - - var streamTypeRef = new TypeReference(corlibScope, "System.IO", "Stream"); - var streamTypeSig = streamTypeRef.ToTypeSignature(); - - -Likewise, a ``TypeSignature`` can also be converted back to a ``ITypeDefOrRef``, which can be referenced using a metadata token, using the ``TypeSignature.ToTypeDefOrRef()`` method. - - -Decorating Types ----------------- - -Type signatures can be annotated with extra properties, such as an array or pointer specifier. - -Below an example of how to create a type signature referencing ``System.Int32[]``: - -.. code-block:: csharp - - var arrayTypeSig = new SzArrayTypeSignature(module.CorLibTypeFactory.Int32); - -Traversing type signature annotations can be done by accessing the ``BaseType`` property of ``TypeSignature``. - -.. code-block:: csharp - - var arrayElementType = arrayTypeSig.BaseType; // returns System.Int32 - -Adding decorations to types can also be done through shortcut methods that follow the ``MakeXXX`` naming scheme: - -.. code-block:: csharp - - var arrayTypeSig = module.CorLibTypeFactory.Int32.MakeSzArrayType(); - -Below an overview of all factory shortcut methods: - -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| Factory method | Description | -+===================================================================+==================================================================================================================+ -| ``MakeArrayType(int dimensionCount)`` | Wraps the type in a new ``ArrayTypeSignature`` with ``dimensionCount`` zero based dimensions with no upperbound. | -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| ``MakeArrayType(ArrayDimension[] dimensions)`` | Wraps the type in a new ``ArrayTypeSignature`` with ``dimensions`` set as dimensions | -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| ``MakeByReferenceType()`` | Wraps the type in a new ``ByReferenceTypeSignature`` | -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| ``MakeModifierType(ITypeDefOrRef modifierType, bool isRequired)`` | Wraps the type in a new ``CustomModifierTypeSignature`` with the specified modifier type. | -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| ``MakePinnedType()`` | Wraps the type in a new ``PinnedTypeSignature`` | -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| ``MakePointerType()`` | Wraps the type in a new ``PointerTypeSignature`` | -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| ``MakeSzArrayType()`` | Wraps the type in a new ``SzArrayTypeSignature`` | -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ -| ``MakeGenericInstanceType(TypeSignature[] typeArguments)`` | Wraps the type in a new ``GenericInstanceTypeSignature`` with the provided type arguments. | -+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ - - - -Comparing Types ---------------- - -Type signatures can be tested for semantic equivalence using the ``SignatureComparer`` class. -Most use-cases of this class will not require any customization. -In these cases, the default ``SignatureComparer`` can be used: - -.. code-block:: csharp - - var comparer = SignatureComparer.Default; - - -However, if you wish to configure the comparer (e.g., for relaxing some of the declaring assembly version comparison rules), it is possible to create a new instance instead: - -.. code-block:: csharp - - var comparer = new SignatureComparer(SignatureComparisonFlags.AllowNewerVersions); - - -Once a comparer is obtained, we can test for type equality using any of the overloaded ``Equals`` methods: - -.. code-block:: csharp - - TypeSignature type1 = ...; - TypeSignature type2 = ...; - - if (comparer.Equals(type1, type2)) - { - // type1 and type2 are semantically equivalent. - } - - -The ``SignatureComparer`` class implements various instances of the ``IEqualityComparer`` interface, and as such, it can be used as a comparer for dictionaries and related types: - -.. code-block:: csharp - - var dictionary = new Dictionary(comparer); - - -.. note:: - - The ``SignatureComparer`` class also implements equality comparers for other kinds of metadata, such as field and method descriptors and their signatures. - - -In some cases, however, exact type equivalence is too strict of a test. -Since .NET facilitates an object oriented environment, many types will inherit or derive from each other, making it difficult to pinpoint exactly which types we would need to compare to test whether two types are compatible with each other. - -Section I.8.7 of the ECMA-335 specification defines a set of rules that dictate whether values of a certain type are compatible with or assignable to variables of another type. -These rules are implemented in AsmResolver using the ``IsCompatibleWith`` and ``IsAssignableTo`` methods: - -.. code-block:: csharp - - if (type1.IsCompatibleWith(type2)) - { - // type1 can be converted to type2. - } - - -.. code-block:: csharp - - if (type1.IsAssignableTo(type2)) - { - // Values of type1 can be assigned to variables of type2. - } diff --git a/docs/dotnet/unmanaged-method-bodies.rst b/docs/dotnet/unmanaged-method-bodies.rst deleted file mode 100644 index 2e733ac28..000000000 --- a/docs/dotnet/unmanaged-method-bodies.rst +++ /dev/null @@ -1,205 +0,0 @@ -Native Method Bodies -==================== - -Method bodies in .NET binaries are not limited to CIL as the implementation language. Mixed-mode applications can contain methods implemented using unmanaged code that runs directly on the underlying processor. Languages might include x86 or ARM, and are always platform-specific. - -AsmResolver supports creating new method bodies that are implemented this way. The relevant models in this document can be found in the following namespaces: - -.. code-block:: csharp - - using AsmResolver.DotNet.Code.Native; - - -Prerequisites -------------- - -Before you can start adding native method bodies to a .NET module, a few prerequisites have to be met. Failing to do so will make the CLR not run your mixed mode application, and might throw runtime or image format exceptions, even if everything else conforms to the right format. This section will go over these requirements briefly. - -Allowing native code in modules -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To make the CLR treat the output file as a mixed mode application, the ``ILOnly`` flag needs to be unset: - -.. code-block:: csharp - - ModuleDefinition module = ... - 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.MachineType = MachineType.Amd64; - module.IsBit32Required = false; - -For 32-bit x86 binaries, use the following: - -.. code-block:: csharp - - module.PEKind = OptionalHeaderMagic.PE32; - module.MachineType = MachineType.I386; - module.IsBit32Required = true; - - -Flags for native methods -~~~~~~~~~~~~~~~~~~~~~~~~ - -As per ECMA-335 specification, a method definition can only represent a native function via Platform Invoke (P/Invoke). While P/Invoke is usually used for importing functions from external libraries (such as `kernel32.dll`), it is also needed for implementing native methods that are defined within the current .NET module itself. Therefore, to be able to assign a valid native body to a method, the right flags need to be set in both the ``Attributes`` and ``ImplAttributes`` property of a ``MethodDefinition``: - -.. code-block:: csharp - - MethodDefinition method = ... - - method.Attributes |= MethodAttributes.PInvokeImpl; - method.ImpleAttributes |= MethodImplAttributes.Native | MethodImplAttributes.Unmanaged | MethodImplAttributes.PreserveSig; - - -The NativeMethodBody class --------------------------- - -The ``MethodDefinition`` class defines a property called ``NativeMethodBody``, which exposes the unmanaged implementation of the method. - -Each ``NativeMethodBody`` is assigned to exactly one ``MethodDefinition``. Upon instantiation of such a method body, it is therefore required to specify the owner of the body: - -.. code-block:: csharp - - MethodDefinition method = ... - - var body = new NativeMethodBody(method); - method.NativeMethodBody = body; - - -The ``NativeMethodBody`` class consists of the following basic building blocks: - -- ``Code``: The raw code stream to be executed. -- ``AddressFixups``: A collection of fixups that need to be applied within the code upon writing the code to the disk. - -In the following sections, we will briefly go over each of them. - -Writing native code -------------------- - -The contents of a native method body can be set through the ``Code`` property. This is a ``byte[]`` that represents the raw code stream to be executed. Below an example of a simple method body written in x86 64-bit assembly code, that returns the constant ``1337``: - -.. code-block:: csharp - - body.Code = new byte[] - { - 0xb8, 0x39, 0x05, 0x00, 0x00, // mov rax, 1337 - 0xc3 // ret - }; - - -.. note:: - - Since native method bodies are platform dependent, AsmResolver does not provide a standard way to encode these instructions. To construct the byte array that you need for a particular implementation of a method body, consider using a third-party assembler or assembler library. - - -Symbols and Address Fixups --------------------------- - -In a lot of cases, native method bodies that references symbols (such as imported functions) require direct addresses to be referenced within its instructions. Since the addresses of these symbols are not known yet upon creating a ``NativeMethodBody``, it is not possible to encode such an operand directly in the ``Code`` byte array. To support these kinds of references regardless, AsmResolver can be instructed to apply address fixups just before writing the body to the disk. These instructions are essentially small pieces of information that tell AsmResolver that at a particular offset the bytes should be replaced with a reference to a symbol in the final PE. This can be applied to any object that implements ``ISymbol``. In the following, two of the most commonly used symbols will be discussed. - - -Imported Symbols -~~~~~~~~~~~~~~~~ - -In the PE file format, symbols from external modules are often imported by placing an entry into the imports directory. This is essentially a table of names that the Windows PE loader will go through, look up the actual address of each name, and put it in the import address table. Typically, when a piece of code is meant to make a call to an external function, the code will make an indirect call to an entry stored in this table. In x86 64-bit, using nasm syntax, a call to the ``puts`` function might look like the following snippet: - -.. code-block:: csharp - - ... - lea rcx, [rel message] - call qword [rel puts] - ... - -Consider the following example x86 64-bit code, that is printing the text ``Hello from the unmanaged world!`` to the standard output stream using the ``puts`` function. - -.. code-block:: csharp - - body.Code = new byte[] - { - /* 00: */ 0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28 - - /* 04: */ 0x48, 0x8D, 0x0D, 0x10, 0x00, 0x00, 0x00, // lea rcx, [rel message] - /* 0B: */ 0xFF, 0x15, 0x00, 0x00, 0x00, 0x00, // call [rel puts] - - /* 11: */ 0xB8, 0x37, 0x13, 0x00, 0x00, // mov eax, 0x1337 - - /* 16: */ 0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28 - /* 1A: */ 0xC3, // ret - - // message: - 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x66, // "Hello f" - 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, // "rom the" - 0x20, 0x75, 0x6e, 0x6d, 0x61, 0x6e, 0x61, // " unmana" - 0x67, 0x65, 0x64, 0x20, 0x77, 0x6f, 0x72, // "ged wor" - 0x6c, 0x64, 0x21, 0x00 // "ld!" - }; - - -Notice how the operand of the ``call`` instruction is left at zero (``0x00``) bytes. To let AsmResolver know that these 4 bytes are to be replaced by an address to an entry in the import address table, we first create a new instance of ``ImportedSymbol``, representing the ``puts`` symbol: - -.. code-block:: csharp - - var ucrtbased = new ImportedModule("ucrtbased.dll"); - var puts = new ImportedSymbol(0x4fc, "puts"); - ucrtbased.Symbols.Add(puts); - - -We can then add it as a fixup to the method body: - -.. code-block:: csharp - - body.AddressFixups.Add(new AddressFixup( - 0xD, AddressFixupType.Relative32BitAddress, puts - )); - - -Local Symbols -~~~~~~~~~~~~~ - -If a native body is supposed to process or return some data that is defined within the body itself, the ``NativeLocalSymbol`` class can be used. - -Consider the following example x86 32-bit snippet, that returns the virtual address of a string. - -.. code-block:: csharp - - 0xB8, 0x00, 0x00, 0x00, 0x00 // mov eax, message - 0xc3, // ret - - // message (unicode): - 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00, // "Hello, " - 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x00, 0x00 // "world!." - - -Notice how the operand of the ``mov`` instruction is left at zero (``0x00``) bytes. To let AsmResolver know that these 4 bytes are to be replaced by the actual virtual address to ``message``, we can define a local symbol and register an address fixup in the following manner: - -.. code-block:: csharp - - var message = new NativeLocalSymbol(body, offset: 0x6); - body.AddressFixups.Add(new AddressFixup( - 0x1, AddressFixupType.Absolute32BitAddress, message - )); - - -.. warning:: - - The ``NativeLocalSymbol`` can only be used within the code of the native method body itself. This is due to the fact that these types of symbols are not processed further after serializing a ``NativeMethodBody`` to a ``CodeSegment`` by the default method body serializer. - - -Fixup Types -~~~~~~~~~~~ - -The type of fixup that is required will depend on the architecture and instruction that is used. Below an overview of all fixups that AsmResolver is able to apply: - -+--------------------------+-----------------------------------------------------------------------+---------------------------------+ -| Fixup type | Description | Example instructions | -+==========================+=======================================================================+=================================+ -| ``Absolute32BitAddress`` | The operand is a 32-bit absolute virtual address | ``call dword [address]`` | -+--------------------------+-----------------------------------------------------------------------+---------------------------------+ -| ``Absolute64BitAddress`` | The operand is a 64-bit absolute virtual address | ``mov rax, address`` | -+--------------------------+-----------------------------------------------------------------------+---------------------------------+ -| ``Relative32BitAddress`` | The operand is an address relative to the current instruction pointer | ``call qword [rip+offset]`` | -+--------------------------+-----------------------------------------------------------------------+---------------------------------+ diff --git a/docs/faq.rst b/docs/faq.rst deleted file mode 100644 index 48e62538a..000000000 --- a/docs/faq.rst +++ /dev/null @@ -1,60 +0,0 @@ -Frequently Asked Questions -========================== - - -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 :ref:`pe-custom-error-handling` (for .NET images: :ref:`dotnet-advanced-module-reading` and :ref:`dotnet-image-builder-diagnostics`). 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 `issues board `_. - - -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 `_). 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 `_. - - -In Mono.Cecil / dnlib my code works, but it does not work in AsmResolver, why? ------------------------------------------------------------------------------- - -Essentially, two things can be happening here: - -- AsmResolver´s code could have a bug. -- You are misusing AsmResolver´s 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 `_. - - -Does AsmResolver have a concept similar to writer events in 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. - -For more details, refer to :ref:`dotnet-advanced-pe-image-building`. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..3ae250636 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,4 @@ +# This is the **HOMEPAGE**. +Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. +## Quick Start Notes: +1. Add images to the *images* folder if the file is referencing an image. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index db82c1e9f..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,85 +0,0 @@ -AsmResolver -=========== - -This is the documentation of the AsmResolver project. AsmResolver is a set of libraries allowing .NET programmers to read, modify and write executable files. This includes .NET as well as native images. The library exposes high-level representations of the PE, while still allowing the user to access low-level structures. - -Table of Contents: ------------------- - -.. toctree:: - :maxdepth: 1 - :caption: General - :name: sec-general - - overview - faq - - -.. toctree:: - :maxdepth: 1 - :caption: Core API - :name: sec-core - - core/segments - - -.. toctree:: - :maxdepth: 1 - :caption: PE Files - :name: sec-pefile - - pefile/index - pefile/basics - pefile/headers - pefile/sections - - -.. toctree:: - :maxdepth: 1 - :caption: PE Images - :name: sec-peimage - - peimage/index - peimage/basics - peimage/advanced-pe-reading - peimage/imports - peimage/exports - peimage/win32resources - peimage/exceptions - peimage/debug - peimage/tls - peimage/dotnet - peimage/pe-building - - -.. toctree:: - :maxdepth: 1 - :caption: .NET assemblies and modules - :name: sec-dotnet - - dotnet/index - dotnet/basics - dotnet/advanced-module-reading - dotnet/member-tree - dotnet/type-signatures - dotnet/importing - dotnet/managed-method-bodies - dotnet/unmanaged-method-bodies - dotnet/dynamic-methods - dotnet/managed-resources - dotnet/cloning - dotnet/token-allocation - dotnet/type-memory-layout - dotnet/bundles - dotnet/advanced-pe-image-building.rst - - - -.. toctree:: - :maxdepth: 1 - :caption: PDB Symbols - :name: sec-pdb - - pdb/index - pdb/basics - pdb/symbols diff --git a/docs/my-template/public/main.css b/docs/my-template/public/main.css new file mode 100644 index 000000000..fb59fbd26 --- /dev/null +++ b/docs/my-template/public/main.css @@ -0,0 +1,22 @@ +article { + line-height: 1.7em; +} + +article h1 { + margin: 20px 0px; +} + +article h2 { + margin: 50px 0px 15px 0px; +} + +article h3 { + margin: 30px 0px 15px 0px; +} + +article code { + border: 1px solid rgba(128,128,128,0.4); + border-radius: 3px; + background: rgba(128,128,128,0.1); + padding: 1px 3px; +} \ No newline at end of file diff --git a/docs/overview.rst b/docs/overview.rst deleted file mode 100644 index 9b2a74ea8..000000000 --- a/docs/overview.rst +++ /dev/null @@ -1,12 +0,0 @@ - -API Overview -============ - -AsmResolver provides three levels of abstraction of a portable executable (PE) file. This may sound complicated at first, but a typical use-case of the library might only need one of them. The three levels are, in increasing level of abstraction: - -* ``PEFile``: The lowest level of abstraction. This layer exposes the raw top-level PE headers, as well as section headers and raw section contents. -* ``PEImage``: This layer exposes interpretations of data directories, such as import and export directories, raw .NET metadata structures and Win32 resources. -* ``AssemblyDefinition`` or ``ModuleDefinition``: (Only relevant to PE files with .NET metadata) Provides a high-level representation of the .NET metadata that is somewhat similar to *System.Reflection*. - -The higher level of abstraction you go, the easier the library is to use and typically the less things you have to worry about. Most people that use AsmResolver to edit .NET applications will probably never even touch the ``PEFile`` or ``PEImage`` class, as ``AssemblyDefinition`` is most likely enough for a lot of use cases. The ``PEFile`` and ``PEImage`` classes are for users that know what they are doing and want to make changes on a lower level. - diff --git a/docs/pdb/basics.rst b/docs/pdb/basics.rst deleted file mode 100644 index a2ad77c05..000000000 --- a/docs/pdb/basics.rst +++ /dev/null @@ -1,113 +0,0 @@ -Basic I/O -========= - -Every PDB image interaction is done through classes defined by the ``AsmResolver.Symbols.Pdb`` namespace: - -.. code-block:: csharp - - using AsmResolver.Symbols.Pdb; - - -Creating a new PDB Image ------------------------- - -Creating a new image can be done by instantiating a ``PdbImage`` class: - -.. code-block:: csharp - - var image = new PdbImage(); - - -Opening a PDB Image -------------------- - -Opening a PDB Image can be done through one of the ``FromXXX`` methods from the ``PdbImage`` class: - -.. code-block:: csharp - - byte[] raw = ... - var image = PdbImage.FromBytes(raw); - -.. code-block:: csharp - - var image = PdbImage.FromFile(@"C:\myfile.pdb"); - -.. code-block:: csharp - - MsfFile msfFile = ... - var image = PdbImage.FromFile(msfFile); - -.. code-block:: csharp - - BinaryStreamReader reader = ... - var image = PdbImage.FromReader(reader); - - -If you want to read large files (+100MB), consider using memory mapped I/O instead: - -.. code-block:: csharp - - using var service = new MemoryMappedFileService(); - var image = PdbImage.FromFile(service.OpenFile(@"C:\myfile.pdb")); - - -Writing a PDB Image -------------------- - -Writing PDB images directly is currently not supported yet, however there are plans to making this format fully serializable. - - -Creating a new MSF File ------------------------ - -Multi-Stream Format (MSF) files are files that form the backbone structure of all PDB images. -AsmResolver fully supports this lower level type of access to MSF files using the ``MsfFile`` class. - -To create a new MSF file, use one of its constructors: - -.. code-block:: csharp - - var msfFile = new MsfFile(); - - -.. code-block:: csharp - - var msfFile = new MsfFile(blockSize: 4096); - - -Opening an MSF File -------------------- - -Opening existing MSF files can be done in a very similar fashion as reading a PDB Image: - -.. code-block:: csharp - - byte[] raw = ... - var msfFile = MsfFile.FromBytes(raw); - -.. code-block:: csharp - - var msfFile = MsfFile.FromFile(@"C:\myfile.pdb"); - -.. code-block:: csharp - - BinaryStreamReader reader = ... - var msfFile = MsfFile.FromReader(reader); - - -Similar to reading PDB images, if you want to read large files (+100MB), consider using memory mapped I/O instead: - -.. code-block:: csharp - - using var service = new MemoryMappedFileService(); - var msfFile = MsfFile.FromFile(service.OpenFile(@"C:\myfile.pdb")); - - -Writing an MSF File -------------------- - -Writing an MSF file can be done through one of the ``Write`` method overloads. - -.. code-block:: csharp - - msfFile.Write(@"C:\myfile.patched.pdb"); diff --git a/docs/pdb/index.rst b/docs/pdb/index.rst deleted file mode 100644 index f6adf7c65..000000000 --- a/docs/pdb/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -Overview -======== - -The Program Database (PDB) file format is a format developed by Microsoft for storing debugging information about binary files. -PDBs are typically constructed based on the original source code the binary was compiled with, and lists various symbols that the source code defines and/or references. - -Since version 5.0, AsmResolver provides a work-in-progress implementation for reading (and sometimes writing) PDB files to allow for better analysis of compiled binaries. -This implementation is fully managed, and thus does not depend on libraries such as the Debug Interface Access (DIA) that only work on the Windows platform. -Furthermore, this project also aims to provide additional documentation on the file format, to make it more accessible to other developers. - -.. warning:: - - As the PDB file format is not very well documented, and mostly is reverse engineered from the official implementation provided by Microsoft, not everything in this API is finalized or stable yet. - As such, this part of AsmResolver's API is still likely to undergo some breaking changes as development continues. \ No newline at end of file diff --git a/docs/pdb/symbols.rst b/docs/pdb/symbols.rst deleted file mode 100644 index 584b6f0d1..000000000 --- a/docs/pdb/symbols.rst +++ /dev/null @@ -1,108 +0,0 @@ -Symbols -======= - -Symbol records define the top-level metadata of a binary, such as public functions, global fields, compiler information, and user-defined type definitions. -They are stored in either the global symbols stream, or in individual module streams. - -In the following, we will discuss various methods for accessing symbols defined in a PDB file. - - -Global Symbols --------------- - -The ``PdbImage`` class defines a ``Symbols`` property, exposing all globally defined symbols in the PDB: - -.. code-block:: csharp - - PdbImage image = ...; - - foreach (var symbol in image.Symbols) - { - Console.WriteLine(symbol); - } - - -You can use LINQ's collection extensions to obtain symbols of a specific type. -For example, the following iterates over all global public symbols: - -.. code-block:: csharp - - PdbImage image = ...; - - foreach (var symbol in image.Symbols.Where(s => s.CodeViewSymbolType == CodeViewSymbolType.Pub32)) - { - Console.WriteLine(symbol); - } - - -Most types of symbol are represented by their own dedicated subclass of ``CodeViewSymbol`` in AsmResolver. -To get a more strongly typed filtering, use the ``OfType()`` extension to also automatically cast to the specific symbol type. -The following iterates over all global public symbols, and automatically casts them to the appropriate ``PublicSymbol`` type: - -.. code-block:: csharp - - PdbImage image = ...; - - foreach (var symbol in image.Symbols.OfType()) - { - Console.WriteLine("Name: {0}, Section: {1}, Offset: {2:X8}.", - symbol.Name, - symbol.SegmentIndex, - symbol.Offset); - } - - -.. note:: - - Since this part of the API is a work-in-process, any symbol that is currently not supported or recognized by AsmResolver's parser will be represented by an instance of the ``UnknownSymbol`` class, exposing the raw data of the symbol. - - -Module Symbols --------------- - -PDB images may define symbols that are only valid within the scope of a single module (typically single compilands and object files). -These can be accessed via the ``PdbImage::Modules`` property, and each module defines its own ``Symbols`` property: - - -.. code-block:: csharp - - PdbImage image = ...; - - foreach (var module in image.Modules) - { - Console.WriteLine(module.Name); - foreach (var symbol in image.Symbols) - Console.WriteLine("\t- {0}", symbol); - Console.WriteLine(); - } - - -Local Symbols -------------- - -Records such as the ``ProcedureSymbol`` class may contain multiple sub-symbols. -These type of symbols all implement ``ICodeViewScopeSymbol``, and define their own set of ``Symbols``. -This way, the symbol tree can be traversed recursively. - -Below is an example snippet of obtaining all local variable symbols defined within the ``DllMain`` function of a library: - -.. code-block:: csharp - - PdbImage image = ...; - - var module = image.Modules.First(m => m.Name == @"c:\simpledll\release\dllmain.obj"); - var procedure = module.Symbols.OfType().First(p => p.Name == "DllMain"); - - foreach (var local in image.Symbols.OfType()) - { - Console.WriteLine("Name: {0}, Type: {1}", - local.Name, - local.VariableType); - } - - -.. note:: - - In the PDB file format, symbols that define a scope (such as ``S_LPROC32`` records) end their scope with a special ``S_END`` symbol record in the file. - However, AsmResolver does **not** include these ending records in the list of symbols. - Ending records are automatically interpreted and inserted when appropriate during the writing process by AsmResolver, and should thus not be expected in the list, nor added to the list manually. \ No newline at end of file diff --git a/docs/pefile/basics.rst b/docs/pefile/basics.rst deleted file mode 100644 index a5636ed60..000000000 --- a/docs/pefile/basics.rst +++ /dev/null @@ -1,80 +0,0 @@ -Basic I/O -========= - -Every raw PE file interaction is done through classes defined by the ``AsmResolver.PE.File`` namespace: - -.. code-block:: csharp - - using AsmResolver.PE.File; - - -Creating a new PE file ----------------------- - -Creating a PE file can be done through one of the ``PEFile`` constructors: - -.. code-block:: csharp - - var peFile = new PEFile(); - - -This will create a new empty PE file with 0 sections, and sets some values in the file header and optional header that are typical for a 32-bit Windows console application targeting the x86 platform. - - -Opening a PE file ------------------ - -Opening a PE file can be done through one of the ``FromXXX`` methods: - -.. code-block:: csharp - - byte[] raw = ... - var peFile = PEFile.FromBytes(raw); - -.. code-block:: csharp - - var peFile = PEFile.FromFile(@"C:\myfile.exe"); - -.. code-block:: csharp - - BinaryStreamReader reader = ... - var peFile = PEFile.FromReader(reader); - - -By default, AsmResolver assumes the PE file is in its unmapped form. This is usually the case when files are read directly from the file system. For memory-mapped PE files, use the overload of the ``FromReader`` method, which allows for specifying the memory layout of the input. - -.. code-block:: csharp - - BinaryStreamReader reader = ... - var peFile = PEFile.FromReader(reader, PEMappingMode.Mapped); - - -If you want to read large files (+100MB), consider using memory-mapped I/O instead: - -.. code-block:: csharp - - using var service = new MemoryMappedFileService(); - var peFile = PEFile.FromFile(service.OpenFile(@"C:\myfile.exe")); - - -On Windows, if a module is loaded and mapped in memory (e.g. as a native dependency or by the means of ``LoadLibrary``), it is possible to load the PE file from memory by providing the ``HINSTANCE`` (a.k.a. module base address): - -.. code-block:: csharp - - IntPtr hInstance = ... - var peFile = PEFile.FromModuleBaseAddress(hInstance); - - -Writing PE files ----------------- - -Writing PE files can be done through the ``PEFile.Write`` method: - -.. code-block:: csharp - - using (var fs = File.Create(@"C:\patched.exe")) - { - peFile.Write(new BinaryStreamWriter(fs)); - } - -AsmResolver will then reassemble the file with all the changes you made. Note that this will also recalculate some fields in the headers, such as ``FileHeader.NumberOfSections``. Furthermore, it will also recalculate the offsets and virtual addresses of each section. \ No newline at end of file diff --git a/docs/pefile/headers.rst b/docs/pefile/headers.rst deleted file mode 100644 index 2b6152271..000000000 --- a/docs/pefile/headers.rst +++ /dev/null @@ -1,218 +0,0 @@ -PE Headers -========== - -After obtaining an instance of the ``PEFile`` class, it is possible to read and edit various properties in the DOS header, COFF file header and optional header. - -All relevant code for this article is found in the following namespace: - -.. code-block:: csharp - - using AsmResolver.PE.File.Headers; - - - -DOS Header ----------- - -The DOS header (also known as the MZ header or ``IMAGE_DOS_HEADER``) is the first header in every PE file, and is represented using the ``DosHeader`` class in AsmResolver. -While the minimal DOS header is 64 bytes long, and often is followed by a stub of MS DOS code, only one field is read and used by Windows while preparing the PE file for execution. -This field (``e_lfanew``) is the offset to the NT Headers (``IMAGE_NT_HEADERS``), which contains the COFF and Optional Header. - -Typically this value is set to ``0x80``, but AsmResolver supports reading and changing this offset if desired: - -.. code-block:: csharp - - PEFile file = ... - - // Obtain e_lfanew: - Console.WriteLine("e_flanew: {0:X8}", file.DosHeader.NextHeaderOffset); - - // Set a new e_lfanew: - file.DosHeader.NextHeaderOffset = 0x100; - - - -File Header ------------ - -The file header describes general characteristics of the PE file. -In particular, it indicates the target architecture, as well as the total size of the optional header and number of sections stored in the PE file. - -AsmResolver exposes the file header via the ``PEFile::FileHeader`` property. -The properties defined in this object correspond directly with the fields in ``IMAGE_FILE_HEADER`` as defined in ``winnt.h``, and are both readable and writeable: - -.. code-block:: csharp - - PEFile file = ... - FileHeader header = file.FileHeader; - - Console.WriteLine($"Machine: {header.Machine}"); - Console.WriteLine("NumberOfSections: {header.NumberOfSections}"); - Console.WriteLine("TimeDateStamp: 0x{header.TimeDateStamp:X8}"); - Console.WriteLine("PointerToSymbolTable: 0x{header.PointerToSymbolTable:X8}"); - Console.WriteLine("NumberOfSymbols: {header.NumberOfSymbols}"); - Console.WriteLine("SizeOfOptionalHeader: 0x{header.SizeOfOptionalHeader:X4}"); - Console.WriteLine("Characteristics: {header.Characteristics}"); - - -.. note:: - - While ``NumberOfSections`` and ``SizeOfOptionalHeader`` are writeable, these properties are automatically updated when using ``PEFile::Write`` to ensure a valid PE file to be written to the disk. - - - -Optional Header ---------------- - -The optional header directly follows the file header of a PE file, and describes information such as the entry point, as well as file alignment and target subsystem. -It also contains the locations of important data directories stored in the PE file containing information such as import address tables and resources. - -AsmResolver exposes the file header via the ``PEFile::OptionalHeader`` property. - -.. code-block:: csharp - - PEFile file = ... - OptionalHeader header = file.OptionalHeader; - - -PE32 and PE32+ Format -~~~~~~~~~~~~~~~~~~~~~ - -While the PE specification defines both a 32-bit and 64-bit version of the structure, AsmResolver abstracts away the differences using a single ``OptionalHeader`` class. -The final file format that is used is dictated by the ``Magic`` property. -Changing the file format can be done by simply writing to this property: - -.. code-block:: csharp - - // Read currently used file format. - Console.WriteLine($"Magic: {header.Magic}"); - - // Change to PE32+ (64-bit format). - header.Magic = OptionalHeaderMagic.PE32Plus; - - -.. warning:: - - For a valid PE file, it is important to use the right file format of the optional header that matches with the target architecture as specified in ``FileHeader::Machine``. - A 32-bit target architecture will always expect a ``PE32`` file format of the optional header, while a 64-bit architecture will require a ``PE32Plus`` format. - AsmResolver does not automatically keep these two properties in sync. - - -Entry Point and Data Directories -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The optional header references many segments in the sections of the PE file via the ``AddressOfEntryPoint`` and ``DataDirectories`` properties. - - -.. code-block:: csharp - - // Reading the entry point: - Console.WriteLine($"AddressOfEntryPoint: 0x{header.AddressOfEntryPoint:X8}"); - - // Setting a new entry point: - header.AddressOfEntryPoint = 0x12345678; - - -Iterating all data directory headers can be done using the following: - -.. code-block:: csharp - - for (int i = 0; i < header.DataDirectories.Count; i++) - { - var directory = header.DataDirectories[i]; - Console.WriteLine($"[{i}]: RVA = 0x{directory.VirtualAddress:X8}, Size = 0x{directory.Size:X8}"); - } - - -Getting or setting a specific data directory header can also be done by using its symbolic index via ``GetDataDirectory`` and ``SetDataDirectory``: - -.. code-block:: csharp - - // Get the import directory. - var directory = header.GetDataDirectory(DataDirectoryIndex.ImportDirectory); - - // Set the import directory. - header.SetDataDirectory(DataDirectoryIndex.ImportDirectory, new DataDirectory( - virtualAddress: 0x00002000, - size: 0x80 - )); - - -Reading the contents behind these pointers can be done e.g., by using ``PEFile::CreateReaderAtRva`` or ``PEFile::CreateDataDirectoryReader``: - -.. code-block:: csharp - - BinaryStreamReader entryPointReader = file.CreateReaderAtRva(header.AddressOfEntryPoint); - - -.. code-block:: csharp - - BinaryStreamReader importsReader = file.CreateDataDirectoryReader( - header.GetDataDirectory(DataDirectoryIndex.ImportDirectory) - ); - - -These functions throw when an invalid offset or size are provided. -It is also possible to use the ``TryCreateXXX`` methods instead, to immediately test for validity and only return the reader if correct information was provided: - -.. code-block:: csharp - - var importDirectory = header.GetDataDirectory(DataDirectoryIndex.ImportDirectory); - if (file.TryCreateDataDirectoryReader(importDirectory, out var importsReader)) - { - // Valid RVA and size. Use importReader to read the contents. - } - - -Sub System -~~~~~~~~~~ - -The ``SubSystem`` field in the optional header defines the type of sub system the executable will be run in. -Typical values are either ``WindowsGui`` for graphical applications, and ``WindowsCui`` for applications requiring a console window. - -.. code-block:: csharp - - // Reading the target sub system: - Console.WriteLine("SubSystem: {header.SubSystem}"); - - // Changing the application to a GUI application: - header.SubSystem = SubSystem.WindowsGui; - - -Section Alignments -~~~~~~~~~~~~~~~~~~ - -The optional header defines two properties ``FileAlignment`` and ``SectionAlignment`` that determine the section alignment stored on the disk and in memory at runtime respectively. - -.. code-block:: csharp - - Console.WriteLine("FileAlignment: 0x{header.FileAlignment}"); - Console.WriteLine("SectionAlignment: 0x{header.SectionAlignment}"); - - -AsmResolver respects the value in ``FileAlignment`` when writing a ``PEFile`` object to the disk, and automatically realigns sections when appropriate. -It is also possible to force the realignment of sections to be done immediately after assigning a new value to these properties using the ``PEFile::AlignSections`` method. - -.. code-block:: csharp - - header.FileAlignment = 0x400; - file.AlignSections(); - - -See :ref:`pe-file-sections` for more information on how to use sections. - - -Other PE Offsets and Sizes -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The optional header defines a few more properties indicating some important offsets and sizes in the PE file: - -- ``SizeOfCode`` -- ``SizeOfInitializedData`` -- ``SizeOfUninitializedData`` -- ``BaseOfCode`` -- ``BaseOfData`` -- ``SizeOfImage`` -- ``SizeOfHeaders`` - -These properties can be read and written in the same way other fields are read and written, but are automatically updated when using ``PEFile::Write`` to ensure a valid binary. \ No newline at end of file diff --git a/docs/pefile/index.rst b/docs/pefile/index.rst deleted file mode 100644 index 913480266..000000000 --- a/docs/pefile/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -Overview -======== - -The PE file layer is the lowest level of abstraction of the portable executable (PE) file format. It's main purpose is to read and write raw executable files from and to the disk. -It is mainly represented by the ``PEFile`` class, and provides access to the raw top-level PE headers, including the DOS header, COFF file header and optional header. -It also exposes for each section the section header and raw contents, which can be read by the means of an ``BinaryStreamReader`` instance. - -It is important to note that this layer mainly leaves the interpretation of the data to the user. You will not find any methods or properties returning models of what is stored in these sections. -Interpretations of e.g. the import directory can be found one layer up, using the ``PEImage`` class. \ No newline at end of file diff --git a/docs/pefile/sections.rst b/docs/pefile/sections.rst deleted file mode 100644 index 5da9a7edc..000000000 --- a/docs/pefile/sections.rst +++ /dev/null @@ -1,119 +0,0 @@ -.. _pe-file-sections: - -PE Sections -=========== - -Sections can be read and modified by accessing the ``PEFile.Sections`` property, which is a collection of ``PESection`` objects. - -.. code-block:: csharp - - PEFile file = ... - - foreach (var section in file.Sections) - { - Console.WriteLine(section.Name); - Console.WriteLine($"\tFile Offset: 0x{section.Offset:X8}"); - Console.WriteLine($"\tRva: 0x{section.Rva:X8}"); - Console.WriteLine($"\tFile Size: 0x{section.Contents.GetPhysicalSize():X8}"); - Console.WriteLine($"\tVirtual Size: 0x{section.Contents.GetVirtualSize():X8}"); - } - - -Reading Section Data -~~~~~~~~~~~~~~~~~~~~ - -Each ``PESection`` object has a ``Contents`` property defined of type ``IReadableSegment``. -This object is capable of creating a ``BinaryStreamReader`` instance to read and parse data from the section: - -.. code-block:: csharp - - var reader = section.CreateReader(); - - -If you want to get the entire section in a byte array, you can take the ``ToArray`` shortcut: - -.. code-block:: csharp - - byte[] data = section.ToArray(); - - -Alternatively, if you have a file offset or RVA to start read from, it is also possible use one of the ``PEFile::CreateReaderAtXXX`` methods: - -.. code-block:: csharp - - var reader = file.CreateReaderAtOffset(0x200); - - -.. code-block:: csharp - - var reader = file.CreateReaderAtRva(0x2000); - - -These methods will automatically find the right section to read from, and provide a reader that points to the start of this data. - - -Adding a new Section -~~~~~~~~~~~~~~~~~~~~ - -The ``Sections`` property is mutable, which means it is possible to add new sections and remove others from the PE. -New sections can be created using the ``PESection`` constructors: - -.. code-block:: csharp - - var section = new PESection(".asmres", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); - section.Contents = new DataSegment(new byte[] {1, 2, 3, 4}); - - file.Sections.Add(section); - - -Some sections (such as ``.data`` or ``.bss``) contain uninitialized data, and might be resized in virtual memory at runtime. -As such, the virtual size of the contents might be different than its physical size. -To make dynamically sized sections, it is possible to use the ``VirtualSegment`` to decorate a normal `ISegment` with a different virtual size. - -.. code-block:: csharp - - var section = new PESection(".asmres", SectionFlags.MemoryRead | SectionFlags.ContentUninitializedData); - var physicalContents = new DataSegment(new byte[] {1, 2, 3, 4}); - section.Contents = new VirtualSegment(physicalContents, 0x1000); // Create a new segment with a virtual size of 0x1000 bytes. - - file.Sections.Add(section); - - -For more advanced section building, see :ref:`pe-building-sections` and :ref:`segments`. - - -Updating Section Offsets -~~~~~~~~~~~~~~~~~~~~~~~~ - -For performance reasons, offsets and sizes are not computed unless you explicitly tell AsmResolver to align all sections and update all offsets within a section. -To force a recomputation of all section offsets and sizes, you can use the ``PEFile::AlignSections`` method: - -.. code-block:: csharp - - PESection section = ...; - file.Sections.Add(section); - - file.AlignSections(); - - Console.WriteLine("New section RVA: 0x{section.Rva:X8}"); - - -If you want to align the sections and also automatically update the fields in the file and optional header of the PE file, it is also possible to use ``PEFile::UpdateHeaders`` instead: - -.. code-block:: csharp - - PESection section = ...; - file.Sections.Add(section); - - file.UpdateHeaders(); - - Console.WriteLine("New section RVA: 0x{section.Rva:X8}"); - Console.WriteLine("New section count: {file.FileHeader.NumberOfSections}"); - - -.. warning:: - - While both ``AlignSections`` and ``UpdateHeaders`` do a traversal of the segment tree, they may not update all offsets and sizes stored in the sections themselves. - When reading a PE file using any of the ``PEFile::FromXXX``, AsmResolver initializes every section's ``Contents`` property with a single contiguous chunk of raw memory, and does not parse any of the section contents. - As such, if some code or data stored in one of these raw section references code or data in another, this will not be automatically updated. - If, however, the ``Contents`` property is an ``ISegment`` that does implement ``UpdateOffsets`` appropriately (e.g., when using a ``SegmentBuilder``), then all references stored in such a segment will be updated accordingly. \ No newline at end of file diff --git a/docs/peimage/advanced-pe-reading.rst b/docs/peimage/advanced-pe-reading.rst deleted file mode 100644 index dd992fd4c..000000000 --- a/docs/peimage/advanced-pe-reading.rst +++ /dev/null @@ -1,121 +0,0 @@ -.. _pe-advanced-image-reading: - -Advanced PE Image Reading -========================= - -Advanced users might have the need to configure AsmResolver's PE image reader. For example, instead of letting the PE reader throw exceptions upon reading invalid data, errors should be ignored and recovered from. Other uses might include a custom interpretation of .NET metadata streams. These kinds of settings can be configured using the ``PEReaderParameters`` class. - -.. code-block:: csharp - - var parameters = new PEReaderParameters(); - -These parameters can then be passed on to any of the ``PEImage.FromXXX`` methods. - -.. code-block:: csharp - - var image = PEImage.FromFile(@"C:\Path\To\File.exe", parameters); - - -.. _pe-custom-error-handling: - -Custom error handling ---------------------- - -By default, AsmResolver throws exceptions upon 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. - -- ``ThrowErrorListener``: Throws the recorded parser exception. This is the default. -- ``EmptyErrorListener``: Silently consumes any parser exception, and allows the reader to recover. -- ``DiagnosticBag``: Collects any parser exceptions, and allows the reader to recover. - -Below an example on how to use the ``DiagnosticBag`` for collecting parser errors and reporting them afterwards. - -.. code-block:: csharp - - var bag = new DiagnosticBag(); - - var image = PEImage.FromFile("...", new PEReaderParameters(bag)); - - /* ... */ - - // Report any errors caught so far to the standard output. - foreach (var error in bag.Exceptions) - Console.WriteLine(error); - - -.. note:: - - The ``PEImage`` class and its derivatives are initialized lazily. As a result, parser errors might not immediately appear in the ``Exceptions`` property of the ``DiagnosticBag`` class. - - -Custom metadata stream reading ------------------------------- - -Some .NET obfuscators insert custom metadata streams in the .NET metadata directory. By default, AsmResolver creates a ``CustomMetadataStream`` object for any metadata stream for which the name is not recognized. To change this behaviour, you can provide a custom implementation of the ``IMetadataStreamReader`` interface, or extend the existing ``DefaultMetadataStreamReader`` class. Below an example of an implementation that changes the way a stream with the name ``#CustomStream`` is parsed: - -.. code-block:: csharp - - public class CustomMetadataStreamReader : DefaultMetadataStreamReader - { - public override IMetadataStream ReadStream( - PEReaderContext context, - MetadataStreamHeader header, - ref BinaryStreamReader reader) - { - if (header.Name == "#CustomStream") - { - // Do custom parsing here. - /* ... */ - } - else - { - // Forward to default stream parser. - base.ReadStream(context, header, ref reader); - } - } - } - - -To let the reader use this implementation of the ``IMetadataStreamReader``, set the ``MetadataStreamReader`` property of the reader parameters. - -.. code-block:: csharp - - parameters.MetadataStreamReader = new CustomMetadataStreamReader(); - - -.. warning:: - - Higher levels of abstractions (e.g. ``AsmResolver.DotNet``) depend on the existence of certain default stream types like the ``TablesStream`` and ``StringsStream``. When these are not provided by your custom implementation, these abstractions will stop working correctly. - - -Custom debug data reading -------------------------- - -Debug data directories can have arbitrary data stored in the PE image. By default, AsmResolver creates for every entry an instance of ``CustomDebugDataSegment``. This can be configured by providing a custom implementation of the ``IDebugDataReader`` interface: - -.. code-block:: csharp - - public class CustomDebugDataReader : DefaultDebugDataReader - { - public override IDebugDataSegment ReadDebugData( - PEReaderContext context, - DebugDataType type, - ref BinaryStreamReader reader) - { - if (type == DebugDataType.Coff) - { - // Do custom parsing here. - /* ... */ - } - else - { - // Forward to default parser. - return base.ReadDebugData(context, type, ref reader); - } - } - } - -To let the reader use this implementation of the ``IDebugDataReader``, set the ``DebugDataReader`` property of the reader parameters. - -.. code-block:: csharp - - parameters.DebugDataReader = new CustomDebugDataReader(); diff --git a/docs/peimage/basics.rst b/docs/peimage/basics.rst deleted file mode 100644 index 88877b7ae..000000000 --- a/docs/peimage/basics.rst +++ /dev/null @@ -1,114 +0,0 @@ -Basic I/O -========= - -Every PE image interaction is done through classes defined by the ``AsmResolver.PE`` namespace: - -.. code-block:: csharp - - using AsmResolver.PE; - -Creating a new PE image ------------------------ - -Creating a new image can be done by instantiating a ``PEImage`` class: - -.. code-block:: csharp - - var peImage = new PEImage(); - - -Opening a PE image ------------------- - -Opening an image can be done through one of the `FromXXX` methods from the ``PEImage`` class: - -.. code-block:: csharp - - byte[] raw = ... - var peImage = PEImage.FromBytes(raw); - -.. code-block:: csharp - - var peImage = PEImage.FromFile(@"C:\myfile.exe"); - -.. code-block:: csharp - - IPEFile peFile = ... - var peImage = PEImage.FromFile(peFile); - -.. code-block:: csharp - - BinaryStreamReader reader = ... - var peImage = PEImage.FromReader(reader); - - -If you want to read large files (+100MB), consider using memory mapped I/O instead: - -.. code-block:: csharp - - using var service = new MemoryMappedFileService(); - var peImage = PEImage.FromFile(service.OpenFile(@"C:\myfile.exe")); - - -On Windows, if a module is loaded and mapped in memory (e.g. as a native dependency or by the means of ``LoadLibrary``), it is possible to load the PE image from memory by providing the ``HINSTANCE`` (a.k.a. module base address): - -.. code-block:: csharp - - IntPtr hInstance = ... - var peImage = PEImage.FromModuleBaseAddress(hInstance); - - -Writing a PE image -------------------- - -Building an image back to a PE file can be done manually by constructing a ``PEFile``, or by using one of the classes that implement the ``IPEFileBuilder`` interface. - -.. note:: - - Currently AsmResolver only provides a full fletched builder for .NET images. - - -Building a .NET image can be done through the ``AsmResolver.PE.DotNet.Builder.ManagedPEFileBuilder`` class: - -.. code-block:: csharp - - var builder = new ManagedPEFileBuilder(); - var newPEFile = builder.CreateFile(image); - -Once a ``PEFile`` instance has been generated from the image, you can use it to write the executable to an output stream (such as a file on the disk or a memory stream). - -.. code-block:: csharp - - using (var stream = File.Create(@"C:\mynewfile.exe")) - { - var writer = new BinaryStreamWriter(stream); - newPEFile.Write(writer); - } - - -For more information on how to construct arbitrary ``PEFile`` instances for native images, look at :ref:`pe-building`. - - -Strong name signing -------------------- - -If the PE image is a .NET image, it can be signed with a strong-name. Open a strong name private key from a file: - -.. code-block:: csharp - - var snk = StrongNamePrivateKey.FromFile(@"C:\Path\To\keyfile.snk"); - -Make sure that the strong name directory is present and has the correct size. - -.. code-block:: csharp - - image.DotNetDirectory.StrongName = new DataSegment(new byte[snk.Modulus.Length]); - -After writing the PE image to an output stream, use the ``StrongNameSigner`` class to sign the image. - -.. code-block:: csharp - - using Stream outputStream = ... - - var signer = new StrongNameSigner(snk); - signer.SignImage(outputStream, module.Assembly.HashAlgorithm); \ No newline at end of file diff --git a/docs/peimage/debug.rst b/docs/peimage/debug.rst deleted file mode 100644 index 30f092147..000000000 --- a/docs/peimage/debug.rst +++ /dev/null @@ -1,59 +0,0 @@ -Debug Directory -=============== - -The debug data directory is used in portable executables to store compiler-generated debug information. In most cases, this information is a reference to a Program Debug Database (``.pdb``) file. - -The relevant classes for this article are stored in the following namespace: - -.. code-block:: csharp - - using AsmResolver.PE.Debug; - - -The Debug Data Entries ----------------------- - -The ``IPEImage`` exposes all debug information through the ``DebugData`` property. This is a list of ``DebugDataEntry``, providing access to the type of debug data, as well as the version and raw contents of the data that is stored. - -.. code-block:: csharp - - foreach (var entry in image.DebugData) - { - Console.WriteLine("Debug Data Type: {0}", entry.Contents.Type); - Console.WriteLine("Version: {0}.{1}", entry.MajorVersion, entry.MinorVersion); - Console.WriteLine("Data start: {0:X8}", entry.Contents.Rva); - } - -Depending on the type of the debug data entry, the ``Contents`` property will be modeled using different implementations of ``IDebugDataSegment``. - -.. note:: - - If a PE contains debug data using an unsupported or unrecognized format, then the contents will be modeled with a ``CustomDebugDataSegment`` instance instead, which exposes the raw contents as an ``ISegment``. - -.. note:: - - Currently, AsmResolver only has rich support for ``CodeView`` debug data. - - -CodeView Data -------------- - -CodeView data is perhaps the most common form of debug data that can appear in a portable executable. It is emitted by a lot of compilers, such as the Visual C++ compiler, and is also used by many .NET languages such as C# and VB.NET. CodeView data is modelled using implementations of the ``CodeViewDataSegment`` abstract class. - -.. code-block:: csharp - - if (entry.Contents.Type == DebugDataType.CodeView) - { - var codeViewData = (CodeViewDataSegment) entry.Contents; - ... - } - -There are various formats used by CodeView data segments, and this format is decided by the ``Signature`` property of the ``CodeViewDataSegment`` class. The most common format used in a CodeView segment, is the RSDS segment. This format stores a path to the Program Debug Database (``*.pdb``) file that is associated to the image. - -.. code-block:: csharp - - if (codeViewData.Signature == CodeViewSignature.Rsds) - { - var rsdsData = (RsdsDataSegment) data; - Console.WriteLine("PDB Path: {0}", rsdsData.Path); - } diff --git a/docs/peimage/dotnet.rst b/docs/peimage/dotnet.rst deleted file mode 100644 index 26cf9626b..000000000 --- a/docs/peimage/dotnet.rst +++ /dev/null @@ -1,363 +0,0 @@ -.NET Data Directories -===================== - -Managed executables (applications written using a .NET language) contain an extra data directory in the optional header of the PE file format. This small data directory contains a header which is also known as the CLR 2.0 header, and references other structures such as the metadata directory, raw data for manifest resources and sometimes an extra native header in the case of mixed mode applications or zapped (ngen'ed) applications. - -.NET directory / CLR 2.0 header -------------------------------- - -The .NET data directory can be accessed by the ``IPEImage.DotNetDirectory`` property. - -.. code-block:: csharp - - IPEImage peImage = ... - - Console.WriteLine("Managed entry point: {0:X8}", peImage.DotNetDirectory.EntryPoint); - - -Metadata directory ------------------------ - -The metadata data directory is perhaps the most important data directory that is referenced by the .NET directory. It contains the metadata streams, such as the table and the blob stream, which play a key role in the execution of a .NET binary. - -To access the metadata directory, access the ``IDotNetDirectory.Metadata`` property, which will provide you an instance of the ``IMetadata`` interface: - -.. code-block:: csharp - - var metadata = peImage.DotNetDirectory.Metadata; - - Console.WriteLine("Metadata file format version: {0}.{1}", metadata.MajorVersion, metadata.MinorVersion); - Console.WriteLine("Target .NET runtime version: " + metadata.VersionString); - - -Metadata streams ---------------------- - -The ``IMetadata`` interface also exposes the ``Streams`` property, a list of ``IMetadataStream`` instances. - -.. code-block:: csharp - - foreach (var stream in metadata.Streams) - Console.WriteLine("Name: " + stream.Name); - -Alternatively, it is possible to get a stream by its name using the ``GetStream(string)`` shortcut: - -.. code-block:: csharp - - var stringsStream = metadata.GetStream("#Strings"); - -Or grab the stream by its type: - -.. code-block:: csharp - - var stringsStream = metadata.GetStream(); - -AsmResolver supports parsing streams using the names in the table below. Any stream with a different name will be converted to a ``CustomMetadataStream``. - -+---------------------------+------------------------+ -| Name | Class | -+===========================+========================+ -| ``#~`` ``#-`` ``#Schema`` | ``TablesStream`` | -+---------------------------+------------------------+ -| ``#Blob`` | ``BlobStream`` | -+---------------------------+------------------------+ -| ``#GUID`` | ``GuidStream`` | -+---------------------------+------------------------+ -| ``#Strings`` | ``StringsStream`` | -+---------------------------+------------------------+ -| ``#US`` | ``UserStringsStream`` | -+---------------------------+------------------------+ - -Some streams support reading the raw contents using a ``BinaryStreamReader``. Effectively, every stream that was read from the disk is readable in this way. Below is an example of a program that dumps for each readable stream the contents to a file on the disk: - -.. code-block:: csharp - - // Iterate over all readable streams. - foreach (var stream in metadata.Streams.Where(s => s.CanRead)) - { - // Create a reader that reads the raw contents of the stream. - var reader = stream.CreateReader(); - - // Write the contents to the disk. - File.WriteAllBytes(stream.Name + ".bin", reader.ReadToEnd()); - } - - -The ``Streams`` property is mutable. You can add new streams, or remove existing streams: - -.. code-block:: csharp - - // Create a new stream with the contents 1, 2, 3, 4. - var data = new byte[] {1, 2, 3, 4}; - var newStream = new CustomMetadataStream("#Custom", data); - - // Add the stream to the metadata directory. - metadata.Streams.Add(newStream); - - // Remove it again. - metadata.Streams.RemoveAt(metadata.Streams.Count - 1); - - -Blob, Strings, US and GUID streams ----------------------------------- - -The blob, strings, user-strings and GUID streams are all very similar in the sense that they all provide a storage for data referenced by the tables stream. Each of these streams has a very similar API in AsmResolver. - -+------------------------+----------------------+ -| Class | Method | -+========================+======================+ -| ``BlobStream`` | ``GetBlobByIndex`` | -+------------------------+----------------------+ -| ``GuidStream`` | ``GetGuidByIndex`` | -+------------------------+----------------------+ -| ``StringsStream`` | ``GetStringByIndex`` | -+------------------------+----------------------+ -| ``UserStringsStream`` | ``GetStringByIndex`` | -+------------------------+----------------------+ - -Example: - -.. code-block:: csharp - - var stringsStream = metadata.GetStream(); - string value = stringsStream.GetStringByIndex(0x1234); - -Since blobs in the blob stream have a specific format, just obtaining the ``byte[]`` of a blob might not be all that useful. Therefore, the ``BlobStream`` has an extra ``GetBlobReaderByIndex`` method, that allows for parsing each blob using an ``BinaryStreamReader`` object instead. If performance is critical, the ``GetBlobReaderByIndex`` method is preferred over ``GetBlobByIndex``, as this method also avoids an allocation of a temporary buffer as well. - -.. code-block:: csharp - - var blobStream = metadata.GetStream(); - if (blobStream.TryGetBlobReaderByIndex(0x1234, out var reader)) - { - // Use reader to parse the blob signature ... - } - -Tables stream -------------- - -The tables stream (``#~``, ``#-`` or ``#Schema``) is the main stream stored in the .NET binary. It provides tables for all members defined in the assembly, as well as all references that the assembly uses. The tables stream is represented by the ``TablesStream`` class and can be obtained in the same way as any other metadata stream: - -.. code-block:: csharp - - TablesStream tablesStream = metadata.GetStream(); - -Metadata tables are represented by the ``IMetadataTable`` interface. Individal tables can be accessed using the ``GetTable` method: - -.. code-block:: csharp - - IMetadataTable typeDefTable = tablesStream.GetTable(TableIndex.TypeDef); - -Tables can also be obtained by their row type: - -.. code-block:: csharp - - MetadataTable typeDefTable = tablesStream.GetTable(); - -The latter option is the preferred option, as it allows for a more type-safe interaction with the table as well and avoids boxing of each row in the table. Each metadata table is associated with its own row structure. Below a table of all row definitions: - -+-------------+-----------------------------+--------------------------------+ -| Table index | Name (as per specification) | AsmResolver row structure name | -+=============+=============================+================================+ -| 0 | Module | ``ModuleDefinitionRow`` | -+-------------+-----------------------------+--------------------------------+ -| 1 | TypeRef | ``TypeReferenceRow`` | -+-------------+-----------------------------+--------------------------------+ -| 2 | TypeDef | ``TypeDefinitionRow`` | -+-------------+-----------------------------+--------------------------------+ -| 3 | FieldPtr | ``FieldPointerRow`` | -+-------------+-----------------------------+--------------------------------+ -| 4 | Field | ``FieldDefinitionRow`` | -+-------------+-----------------------------+--------------------------------+ -| 5 | MethodPtr | ``MethodPointerRow`` | -+-------------+-----------------------------+--------------------------------+ -| 6 | Method | ``MethodDefinitionRow`` | -+-------------+-----------------------------+--------------------------------+ -| 7 | ParamPtr | ``ParameterPointerRow`` | -+-------------+-----------------------------+--------------------------------+ -| 8 | Param | ``ParameterDefinitionRow`` | -+-------------+-----------------------------+--------------------------------+ -| 9 | InterfaceImpl | ``InterfaceImplementationRow`` | -+-------------+-----------------------------+--------------------------------+ -| 10 | MemberRef | ``MemberReferenceRow`` | -+-------------+-----------------------------+--------------------------------+ -| 11 | Constant | ``ConstantRow`` | -+-------------+-----------------------------+--------------------------------+ -| 12 | CustomAttribute | ``CustomAttributeRow`` | -+-------------+-----------------------------+--------------------------------+ -| 13 | FieldMarshal | ``FieldMarshalRow`` | -+-------------+-----------------------------+--------------------------------+ -| 14 | DeclSecurity | ``SecurityDeclarationRow`` | -+-------------+-----------------------------+--------------------------------+ -| 15 | ClassLayout | ``ClassLayoutRow`` | -+-------------+-----------------------------+--------------------------------+ -| 16 | FieldLayout | ``FieldLayoutRow`` | -+-------------+-----------------------------+--------------------------------+ -| 17 | StandAloneSig | ``StandAloneSignatureRow`` | -+-------------+-----------------------------+--------------------------------+ -| 18 | EventMap | ``EventMapRow`` | -+-------------+-----------------------------+--------------------------------+ -| 19 | EventPtr | ``EventPointerRow`` | -+-------------+-----------------------------+--------------------------------+ -| 20 | Event | ``EventDefinitionRow`` | -+-------------+-----------------------------+--------------------------------+ -| 21 | PropertyMap | ``PropertyMapRow`` | -+-------------+-----------------------------+--------------------------------+ -| 22 | PropertyPtr | ``PropertyPointerRow`` | -+-------------+-----------------------------+--------------------------------+ -| 23 | Property | ``PropertyDefinitionRow`` | -+-------------+-----------------------------+--------------------------------+ -| 24 | MethodSemantics | ``MethodSemanticsRow`` | -+-------------+-----------------------------+--------------------------------+ -| 25 | MethodImpl | ``MethodImplementationRow`` | -+-------------+-----------------------------+--------------------------------+ -| 26 | ModuleRef | ``ModuleReferenceRow`` | -+-------------+-----------------------------+--------------------------------+ -| 27 | TypeSpec | ``TypeSpecificationRow`` | -+-------------+-----------------------------+--------------------------------+ -| 28 | ImplMap | ``ImplementatinoMappingRow`` | -+-------------+-----------------------------+--------------------------------+ -| 29 | FieldRva | ``FieldRvaRow`` | -+-------------+-----------------------------+--------------------------------+ -| 30 | EncLog | ``EncLogRow`` | -+-------------+-----------------------------+--------------------------------+ -| 31 | EncMap | ``EncMapRow`` | -+-------------+-----------------------------+--------------------------------+ -| 32 | Assembly | ``AssemblyDefinitionRow`` | -+-------------+-----------------------------+--------------------------------+ -| 33 | AssemblyProcessor | ``AssemblyProcessorRow`` | -+-------------+-----------------------------+--------------------------------+ -| 34 | AssemblyOS | ``AssemblyOSRow`` | -+-------------+-----------------------------+--------------------------------+ -| 35 | AssemblyRef | ``AssemblyReferenceRow`` | -+-------------+-----------------------------+--------------------------------+ -| 36 | AssemblyRefProcessor | ``AssemblyRefProcessorRow`` | -+-------------+-----------------------------+--------------------------------+ -| 37 | AssemblyRefOS | ``AssemblyRefOSRow`` | -+-------------+-----------------------------+--------------------------------+ -| 38 | File | ``FileReferenceRow`` | -+-------------+-----------------------------+--------------------------------+ -| 39 | ExportedType | ``ExportedTypeRow`` | -+-------------+-----------------------------+--------------------------------+ -| 40 | ManifestResource | ``ManifestResourceRow`` | -+-------------+-----------------------------+--------------------------------+ -| 41 | NestedClass | ``NestedClassRow`` | -+-------------+-----------------------------+--------------------------------+ -| 42 | GenericParam | ``GenericParamRow`` | -+-------------+-----------------------------+--------------------------------+ -| 43 | MethodSpec | ``MethodSpecificationRow`` | -+-------------+-----------------------------+--------------------------------+ -| 44 | GenericParamConstraint | ``GenericParamConstraintRow`` | -+-------------+-----------------------------+--------------------------------+ - -Metadata tables are similar to normal ``ICollection`` instances. They provide enumerators, indexers and methods to add or remove rows from the table. - -.. code-block:: csharp - - Console.WriteLine("Number of types: " + typeDefTable.Count); - - // Get a single row. - TypeDefinitionRow firstTypeRow = typeDefTable[0]; - - // Iterate over all rows: - foreach (var typeRow in typeDefTable) - { - // ... - } - -Members can also be accessed by their RID using the ``GetByRid`` or ``TryGetByRid`` helper functions: - -.. code-block:: csharp - - TypeDefinitionRow thirdTypeRow = typeDefTable.GetByRid(3); - -Using the other metadata streams, it is possible to resolve all columns. Below an example that prints the name and namespace of each type row in the type definition table in a file. - -.. code-block:: csharp - - // Load PE image. - var peImage = PEImage.FromFile(@"C:\file.exe"); - - // Obtain relevant streams. - var metadata = peImage.DotNetDirectory.Metadata; - var tablesStream = metadata.GetStream(); - var stringsStream = metadata.GetStream(); - - // Go over each type definition in the file. - var typeDefTable = tablesStream.GetTable(); - foreach (var typeRow in typeDefTable) - { - // Resolve name and namespace columns using the #Strings stream. - string ns = stringsStream.GetStringByIndex(typeRow.Namespace); - string name = stringsStream.GetStringByIndex(typeRow.Name); - - // Print name and namespace: - Console.WriteLine(string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"); - } - - -Method and FieldRVA -------------------- - -Every row structure defined in AsmResolver respects the specification described by the CLR itself. However, there are two exceptions to this rule, and those are the **Method** and **FieldRVA** rows. According to the specification, both of these rows have an **RVA** column that references a segment in the original PE file. Since this second layer of abstraction attempts to abstract away any file offset or virtual address, these columns are replaced with properties called ``Body`` and ``Data`` respectively, both of type ``ISegmentReference`` instead. - -``ISegmentReference`` exposes a method ``CreateReader()``, which automatically resolves the RVA that was stored in the row, and creates a new input stream that can be used to parse e.g. method bodies or field data. - - -Reading method bodies: -~~~~~~~~~~~~~~~~~~~~~~ - -Reading a managed CIL method body can be done using ``CilRawMethodBody.FromReader`` method: - -.. code-block:: csharp - - var methodTable = tablesStream.GetTable(); - var firstMethod = methodTable[0]; - var methodBody = CilRawMethodBody.FromReader(firstMethod.Body.CreateReader()); - -It is important to note that the user is not bound to use ``CilRawMethodBody``. In the case that the ``Native`` (``0x0001``) flag is set in ``MethodDefinitionRow.ImplAttributes``, the implementation of the method body is not written in CIL, but using native code that uses an instruction set dependent on the platform that this application is targeting. Since the bounds of such a method body is not always well-defined, AsmResolver does not do any parsing on its own. However, using the ``CreateReader()`` method, it is still possible to decode instructions from this method body, using a custom instruction decoder. - - -Reading field data: -~~~~~~~~~~~~~~~~~~~ - -Reading field data can be done in a similar fashion as reading method bodies. Again use the ``CreateReader()`` method to gain access to the raw data of the initial value of the field referenced by a **FieldRVA** row. - -.. code-block:: csharp - - var fieldRvaTable = tablesStream.GetTable(); - var firstRva = fieldRvaTable[0]; - var reader = firstRva.Data.CreateReader(); - - -Creating new segment references: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Creating new segment references not present in the current PE image yet can be done using the ``ISegment.ToReference()`` extension method: - -.. code-block:: csharp - - var myData = new DataSegment(new byte[] {1, 2, 3, 4}); - var fieldRva = new FieldRvaRow(myData.ToReference(), 0); - - - -.. _pe-typereference-hash: - -TypeReference Hash (TRH) ------------------------- - -Similar to the :ref:`pe-import-hash`, the TypeReference Hash (TRH) can be used to help identify malware families written in a .NET language. However, unlike the Import Hash, the TRH is based on the names of all imported type references instead of the symbols specified in the imports directory of the PE. This is a more accurate representation for .NET images, as virtually every .NET image only uses one native symbol (either ``mscoree.dll!_CorExeMain`` or ``mscoree.dll!_CorDllMain``). - -AsmResolver includes a built-in implementation for this that is based on `the reference implementation provided by GData `_. The hash can be obtained using the ``GetTypeReferenceHash`` extension method on ``IPEImage`` or on ``IMetadata``: - -.. code-block:: csharp - - IPEImage image = ... - byte[] hash = image.GetTypeReferenceHash(); - - -.. code-block:: csharp - - IMetadata metadata = ... - byte[] hash = metadata.GetTypeReferenceHash(); - diff --git a/docs/peimage/exceptions.rst b/docs/peimage/exceptions.rst deleted file mode 100644 index 3fc6aba8e..000000000 --- a/docs/peimage/exceptions.rst +++ /dev/null @@ -1,56 +0,0 @@ -Exceptions Directory -==================== - -Structured Exception Handling (SEH) in native programming languages such as C++ are for some platforms implemented using exception handler tables. These are tables that store ranges of addresses that are protected by an exception handler. In the PE file format, these tables are stored in the Exceptions Data Directory. - -The relevant classes for this article are stored in the following namespace: - -.. code-block:: csharp - - using AsmResolver.PE.Exceptions; - - -Runtime Functions ------------------ - -The ``IPEImage`` interface exposes these tables through the ``Exceptions`` property. This is of type ``IExceptionsDirectory``, which allows for read access to instances of ``IRuntimeFunction`` through the ``GetEntries`` method. This interface models all the runtime functions through the method, in a platform agnostic manner. - -.. code-block:: csharp - - foreach (var function in peImage.Exceptions.GetEntries()) - { - Console.WriteLine("Begin: {0:X8}.", function.Begin.Rva); - Console.WriteLine("End: {0:X8}.", function.End.Rva); - } - - -Different platforms use different physical formats for their runtime functions. To figure out what kind of format an image is using, check the ``Machine`` field in the file header of the PE file. - -.. note:: - - Currently AsmResolver only supports reading exception tables of PE files targeting the AMD64 (x86 64-bit) architecture. - - -AMD64 / x64 ------------ - -AsmResolver provides the ``X64ExceptionsDirectory`` and ``X64RuntimeFunction`` classes that implement the exceptions directory for the AMD64 (x86 64-bit) architecture. Next to just the start and end address, this implementation also provides access to the unwind info. - -.. code-block:: csharp - - var directory = (X64ExceptionsDirectory) peImage.Exceptions; - - foreach (var function in directory.Entries) - { - Console.WriteLine("Begin: {0:X8}.", function.Begin.Rva); - Console.WriteLine("End: {0:X8}.", function.End.Rva); - - var unwindInfo = function.UnwindInfo; - - // Get handler start. - Console.WriteLine("Handler Start: {0:X8}.", unwindInfo.ExceptionHandler.Rva); - - // Read custom SEH data associated to this unwind information. - var dataReader = function.ExceptionHandlerData.CreateReader(); - // ... - } \ No newline at end of file diff --git a/docs/peimage/exports.rst b/docs/peimage/exports.rst deleted file mode 100644 index 5994c7e24..000000000 --- a/docs/peimage/exports.rst +++ /dev/null @@ -1,35 +0,0 @@ -Exports Directory -================= - -Dynamically linked libraries (DLLs) often expose symbols through defining exports in the exports directory. - -The ``IPEImage`` interface exposes the ``Exports`` property, exposing a mutable instance of `ExportDirectory`, which defines the following properties: - -- ``Name``: The name of the dynamically linked library. -- ``BaseOrdinal``: The base ordinal of all exported symbols. -- ``Entries``: A list of exported symbols. - -Exported symbols are represented using the ``ExportedSymbol`` class, which defines: - -- ``Name``: The name of the exported symbol. If this value is ``null``, the symbol is exported by ordinal rather than by name. -- ``Ordinal``: The ordinal of the exported symbol. This property is automatically updated when ``BaseOrdinal`` of the parent directory is updated. -- ``Address``: A reference to the segment representing the symbol. - - For exported functions, this reference points to the first instruction that is executed. - - For exported fields, this reference points to the first byte of data that this field consists of. - - -Example -------- - -Below is an example of a program that lists all symbols exported by a given ``IPEImage`` instance: - -.. code-block:: csharp - - foreach (var symbol in peImage.Exports.Entries) - { - Console.WriteLine("Ordinal: " + symbol.Ordinal); - if (symbol.IsByName) - Console.WriteLine("Name: " + symbol.Name); - Console.WriteLine("Address: " + symbol.Address.Rva.ToString("X8")); - } - diff --git a/docs/peimage/imports.rst b/docs/peimage/imports.rst deleted file mode 100644 index 0598817c7..000000000 --- a/docs/peimage/imports.rst +++ /dev/null @@ -1,68 +0,0 @@ -Imports Directory -================= - -Most portable executables import functions and fields from other, external libraries. These are stored in a table in the imports data directory of the PE file. Each entry in this table defines a module that is loaded at runtime, and a set of members that are looked up. - -All code relevant to the imports directory of a PE resides in the following namespace: - -.. code-block:: csharp - - using AsmResolver.PE.Imports; - - -Imported Modules and Symbols ----------------------------- - -The ``IPEImage`` interface exposes the ``Imports`` property, which contains all members that are resolved at runtime, grouped by the defining module. Below an example of a program that lists all members imported by a given ``IPEImage`` instance: - -.. code-block:: csharp - - foreach (var module in peImage.Imports) - { - Console.WriteLine("Module: " + module.Name); - - foreach (var member in module.Symbols) - { - if (member.IsImportByName) - Console.WriteLine("\t- " + member.Name); - else - Console.WriteLine("\t- #" + member.Ordinal); - } - - Console.WriteLine(); - } - - -.. _pe-import-hash: - -Import Hash ------------ - -An Import Hash (ImpHash) of a PE image is a hash code that is calculated based on all symbols imported by the image. `Originally introduced in 2014 by Mandiant `_, an Import Hash can be used to help identifying malware families quickly, as malware samples that belong to the same family often have the same set of dependencies. - -AsmResolver provides a built-in implementation for calculating the Import Hash. The hash can be obtained by using the ``GetImportHash`` extension method on ``IPEImage``: - -.. code-block:: csharp - - IPEImage image = ... - byte[] hash = image.GetImportHash(); - - -Since the hash is computed based on the names of all imported symbols, symbols that are imported by ordinal need to be resolved. By default, AsmResolver uses a static lookup table for a couple of common Windows libraries, but should this resolution process be customized, then this can be done by providing a custom instance of ``ISymbolResolver``: - -.. code-block:: csharp - - public class MySymbolResolver : ISymbolResolver - { - public ExportedSymbol? Resolve(ImportedSymbol symbol) - { - /* Resolve symbol by ordinal here... */ - } - } - - IPEImage image = ... - byte[] hash = image.GetImportHash(new MySymbolResolver()); - - - -While the Import Hash can be a good identifier for native PE images, for .NET images this is not the case. .NET images usually only import a single external symbol (either ``mscoree.dll!_CorExeMain`` or ``mscoree.dll!_CorDllMain``), and as such they will almost always have the exact same Import Hash. See :ref:`pe-typereference-hash` for an alternative for .NET images. \ No newline at end of file diff --git a/docs/peimage/index.rst b/docs/peimage/index.rst deleted file mode 100644 index 8f761d70a..000000000 --- a/docs/peimage/index.rst +++ /dev/null @@ -1,4 +0,0 @@ -Overview -======== - -The PE image layer is the second layer of abstraction of the portable executable (PE) file format. It is mostly represented by ``IPEImage`` and its derivatives (such as ``PEImage``), and works on top of the ``PEFile`` layer. Its main purpose is to provide access to mutable models that are easier to use than the raw data structures the PE file layer exposes. diff --git a/docs/peimage/pe-building.rst b/docs/peimage/pe-building.rst deleted file mode 100644 index 51a21c479..000000000 --- a/docs/peimage/pe-building.rst +++ /dev/null @@ -1,173 +0,0 @@ -.. _pe-building: - -PE File Building -================ - -An ``IPEImage`` is a higher-level abstraction of an actual PE file, and hides many of the actual implementation details such as raw section layout and contents of data directories. -While this makes reading and interpreting a PE very easy, it makes writing a more involved process. - -Unfortunately, the PE file format is not well-structured. -Compilers and linkers have a lot of freedom when it comes to the actual design and layout of the final PE file. -For example, one compiler might place the Import Address Table (IAT) in a separate section (such as the MSVC), while others will put it next to the code in the text section (such as the C# compiler). -This degree of freedom makes it virtually impossible to make a completely generic PE file builder, and as such AsmResolver does not come with one out of the box. - -It is still possible to build PE files from instances of ``IPEImage``, it might just take more manual labor than you might expect at first. -This article will discuss certain strategies that can be adopted when constructing new PE files from PE images. - - -.. _pe-building-sections: - -Building Sections ------------------ - -As discussed in :ref:`pe-file-sections`, the ``PESection`` class represents a single section in the PE file. -This class exposes a property called ``Contents`` which is of type ``ISegment``. -A lot of models in AsmResolver implement this interface, and as such, these models can directly be used as the contents of a new section. - -.. code-block:: csharp - - var peFile = new PEFile(); - - var text = new PESection(".text", - SectionFlags.ContentCode | SectionFlags.MemoryRead | SectionFlags.MemoryExecute); - text.Content = new DataSegment(new byte[] { ... } ); - peFile.Sections.Add(text); - - -In a lot of cases, however, sections do not consist of a single data structure, but often comprise many segments concatenated together, potentially with some padding in between. -To compose new segments from a collection of smaller sub-segments structures, the ``SegmentBuilder`` class can be used. -This is a class that combines a collection of ``ISegment`` instances into one, allowing you to concatenate segments that belong together easily. -Below an example that builds up a ``.text`` section that consists of a .NET data directory as well as some native code. -In the example, the native code is aligned to a 4-byte boundary: - -.. code-block:: csharp - - var peFile = new PEFile(); - - var textBuilder = new SegmentBuilder(); - textBuilder.Add(new DotNetDirectory { ... }); - textBuilder.Add(new DataSegment(new byte[] { ... } ), alignment: 4); - - var text = new PESection(".text", - SectionFlags.ContentCode | SectionFlags.MemoryRead | SectionFlags.MemoryExecute); - text.Content = sectionBuilder; - - peFile.Sections.Add(text); - - -PE files can also be patched using the ``PatchedSegment`` API. -Below is an example of patching some data in the ``.text`` section of a PE File with some literal data ``{1, 2, 3, 4}``, as well as writing an address to a symbol in the imports table: - -.. code-block:: csharp - - var peFile = PEFile.FromFile("input.exe"); - var section = peFile.Sections.First(s => s.Name == ".text"); - - var someSymbol = peImage - .Imports.First(m => m.Name == "ucrtbased.dll") - .Symbols.First(s => s.Name == "puts"); - - section.Contents = section.Contents.AsPatchedSegment() // Create patched segment. - .Patch(offset: 0x10, data: new byte[] {1, 2, 3, 4}) // Apply literal bytes patch - .Patch(offset: 0x20, AddressFixupType.Absolute64BitAddress, someSymbol); // Apply address fixup patch. - - -More detailed information on how to use segments can be found in :ref:`segments`. - - -Using complex PE models ------------------------ - -The PE file format defines many complex data structures. -While some of these models are represented in AsmResolver using classes that derive from ``ISegment``, a lot of the classes that represent these data structures do not implement this interface, and as such cannot be used as a value for the ``Contents`` property of a ``PESection`` directly. -This is due to the fact that most of these models are not required to be one single entity or chunk of continuous memory within the PE file. Instead, they are often scattered around the PE file by a compiler. -For example, the Import Directory has a second component the Import Address Table which is often put in a completely different PE section (usually ``.text`` or ``.data``) than the Import Directory itself (in ``.idata`` or ``.rdata``). -To make reading and interpreting these data structures more convenient for the end-user, the ``AsmResolver.PE`` package adopted some design choices to abstract these details away to make things more natural to work with. -The downside of this is that writing these structures requires you to specify where AsmResolver should place these models in the final PE file. - -In ``AsmResolver.PE``, most models for which is the case reside in their own namespace, and have their own set of classes dedicated to constructing new segments defined in a ``Builder`` sub-namespace. -For example, the Win32 resources directory models reside in ``AsmResolver.PE.Win32Resources``, but the actual builder classes are put in a sub namespace called ``AsmResolver.PE.Win32Resources.Builder``. - -.. code-block:: csharp - - IPEImage image = ... - - // Construct a resources directory. - var resources = new ResourceDirectoryBuffer(); - resources.AddDirectory(image.Resources); - - var file = new PEFile(); - - // Place in a read-only section. - var rsrc = new PESection(".rsrc", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); - rsrc.Contents = resources; - file.Sections.Add(rsrc); - - -A more complicated structure such as the Imports Directory can be build like the following: - -.. code-block:: csharp - - IPEImage image = ... - - // Construct an imports directory. - var buffer = new ImportDirectoryBuffer(); - foreach (var module in image.Imports) - buffer.AddModule(module); - - var file = new PEFile(); - - // Place import directory in a read-only section. - var rdata = new PESection(".rdata", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); - rdata.Contents = buffer; - file.Sections.Add(rdata); - - // Place the IAT in a writable section. - var data = new PESection(".data", SectionFlags.MemoryRead | SectionFlags.MemoryWrite | SectionFlags.ContentInitializedData); - data.Contents = buffer.ImportAddressDirectory; - file.Sections.Add(ddata); - - -Using PEFileBuilders --------------------- - -As a lot of the PE file-building process will be similar for many types of PE file layouts (such as the construction of the file and optional headers), AsmResolver comes with a base class called ``PEFileBuilderBase`` that abstracts many of these similarities away. -Rather than defining and building up everything yourself, the ``PEFileBuilderBase`` allows you to override a couple of methods: - -.. code-block:: csharp - - public class MyPEFileBuidler : PEFileBuilderBase - { - protected override MyBuilderContext CreateContext(IPEImage image) => new(); - - protected override uint GetFileAlignment(PEFile peFile, IPEImage image, MyBuilderContext context) => 0x200; - - protected override uint GetSectionAlignment(PEFile peFile, IPEImage image, MyBuilderContext context) => 0x2000; - - protected override uint GetImageBase(PEFile peFile, IPEImage image, MyBuilderContext context) => 0x00400000; - - protected override IEnumerable CreateSections(IPEImage image, MyBuilderContext context) - { - /* Create sections here */ - } - - protected override IEnumerable CreateDataDirectories(PEFile peFile, IPEImage image, MyBuilderContext context) - { - /* Create data directories here */ - } - } - - public class MyBuilderContext - { - /* Define here additional state data to be used in your builder. */ - } - - -This can then be used like the following: - -.. code-block:: csharp - - IPEImage image = ... - - var builder = new MyPEFileBuilder(); - PEFile file = builder.CreateFile(image); diff --git a/docs/peimage/tls.rst b/docs/peimage/tls.rst deleted file mode 100644 index 938d4dc74..000000000 --- a/docs/peimage/tls.rst +++ /dev/null @@ -1,67 +0,0 @@ -TLS Directory -============= - -Executables that use multiple threads might require static (non-stack) memory that is local to a specific thread. The PE file format allows for defining segments of memory within the file that specifies what this memory should like, and how it should be initialized. This information is stored inside the Thread Local Storage (TLS) data directory. - -All code relevant to the TLS data directory of a PE resides in the following namespace: - -.. code-block:: csharp - - using AsmResolver.PE.Tls; - - -Template Data, Zero Fill and Index ----------------------------------- - -The PE file format defines a segment of memory within the TLS data directory that specifies how the thread local data should be initialized. This is represented using the following three properties: - -- ``TemplateData``: A segment representing the initial data of the static memory. -- ``SizeOfZeroFill``: The number of extra zeroes appended to the end of the initial data as specified by ``TemplateData`` -- ``Index``: A reference that will receive the thread index. This is supposed to be a reference in a writable PE section (typically ``.data``). - - -.. code-block:: csharp - - var indexSegment = new DataSegment(new byte[8]); - - var directory = new TlsDirectory - { - TemplateData = new DataSegment(new byte[] { ... }), - SizeOfZeroFill = 0x1000, - Index = indexSegment.ToReference() - }; - - -TLS Callback Functions ----------------------- - -Next to static initialization data, it is also possible to specify a list of functions called TLS Callbacks that are supposed to further initialize the thread local storage. This is exposed through the ``CallbackFunctions`` property, which is a list of references to the start of every TLS callback function. - -.. code-block:: csharp - - for (int i = 0; i < directory.CallbackFunctions.Count; i++) - { - Console.WriteLine("TLS Callback #{0}: {1:X8}", directory.CallbackFunctions.Rva); - } - - -Creating new TLS directories ----------------------------- - -Adding a new TLS directory to an image can be done using the parameterless constructor of the ``TlsDirectory`` class: - -.. code-block:: csharp - - var tlsDirectory = new TlsDirectory(); - image.TlsDirectory = tlsDirectory; - -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 - - using AsmResolver.PE.Relocations; - - IPEImage image = ...; - - foreach (var relocation in tlsDirectory.GetRequiredBaseRelocations()) - image.Relocations.Add(relocation); diff --git a/docs/peimage/win32resources.rst b/docs/peimage/win32resources.rst deleted file mode 100644 index 6a53bcf05..000000000 --- a/docs/peimage/win32resources.rst +++ /dev/null @@ -1,146 +0,0 @@ -Win32 Resources -=============== - -Win32 resources are additional files embedded into the PE image, and are typically stored in the ``.rsrc`` section. All classes relevant to Win32 resources can be found in the following namespace: - -.. code-block:: csharp - - using AsmResolver.PE.Win32Resources; - - -Directories ------------ - -Resources are exposed by the ``IPEImage.Resources`` property, which represents the root directory of all resources stored in the image. Every directory (including the root directory) is represented by instances of ``IResourceDirectory``. This type contains the ``Entries`` property. Entries in a directory can either be another sub directory containing more entries, or a data entry (an instance of ``IResourceData``) with the raw contents of the resource. - -.. code-block:: csharp - - IPEImage image = ... - IResourceDirectory root = image.Resources; - - foreach (var entry in root.Entries) - { - if (entry.IsDirectory) - Console.WriteLine("Directory {0}.", entry.Id); - else // if (entry.IsData) - Console.WriteLine("Data {0}.", entry.Id); - } - - -Alternatively, you can access specific resources very easily by using the ``GetDirectory`` and ``GetData``: - -.. code-block:: csharp - - IPEImage image = ... - IResourceData stringDataEntry = image.Resources - .GetDirectory(ResourceType.String) // Get string tables directory. - .GetDirectory(251) // Get string block with ID 251 - .GetData(1033); // Get string with language ID 1033 - - -Adding or replacing entries can be by either modifying the ``Entries`` property directly, or by using the ``AddOrReplace`` method. The latter is recommended as it ensures that an existing entry with the same ID is replaced with the new one. - -.. code-block:: csharp - - IPEImage image = ... - var newDirectory = new ResourceDirectory(ResourceType.String); - image.Resources.Entries.Add(newDirectory); - - -.. code-block:: csharp - - IPEImage image = ... - var newDirectory = new ResourceDirectory(ResourceType.String); - image.Resources.AddOrReplaceEntry(newDirectory); - - -Similarly, removing can be done by modifying the ``Entries`` directory, or by using the ``RemoveEntry`` method: - -.. code-block:: csharp - - IPEImage image = ... - image.Resources.RemoveEntry(ResourceType.String); - - -Data Entries ------------- - -Data entries are represented using the ``IResourceData`` interface, and contain a property called ``Contents`` which is of type ``ISegment``. You can check if this is a ``IReadableSegment``, or use the shortcuts ``CanRead`` and ``CreateReader`` methods instead to read the raw contents of the entry. - -.. code-block:: csharp - - IPEImage image = ... - IResourceData dataEntry = image.Resources - .GetDirectory(ResourceType.String) // Get string tables directory. - .GetDirectory(251) // Get string block with ID 251 - .GetData(1033); // Get string with language ID 1033 - - if (dataEntry.CanRead) - { - BinaryStreamReader reader = dataEntry.CreateReader(); - // ... - } - - -Adding new data entries can be done by using the ``ResourceData`` constructor: - -.. code-block:: csharp - - IPEImage image = ... - - var data = new ResourceData(id: 1033, contents: new DataSegment(new byte[] { ... })); - image.Resources - .GetDirectory(ResourceType.String) - .GetDirectory(251) - .AddOrReplaceEntry(data); - - -Example Traversal ------------------ - -The following example is a program that dumps the resources tree from a single PE image. - -.. code-block:: csharp - - private const int IndentationWidth = 3; - - private static void Main(string[] args) - { - // Open the PE image. - string filePath = args[0].Replace("\"", ""); - var peImage = PEImage.FromFile(filePath); - - // Dump the resources. - PrintResourceDirectory(peImage.Resources); - } - - private static void PrintResourceEntry(IResourceEntry entry, int indentationLevel = 0) - { - // Decide if we are dealing with a sub directory or a data entry. - if (entry.IsDirectory) - PrintResourceDirectory((IResourceDirectory) entry, indentationLevel); - else if (entry.IsData) - PrintResourcData((IResourceData) entry, indentationLevel); - } - - private static void PrintResourceDirectory(IResourceDirectory directory, int indentationLevel = 0) - { - string indentation = new string(' ', indentationLevel * IndentationWidth); - - // Print the name or ID of the directory. - string displayName = directory.Name ?? "ID: " + directory.Id; - Console.WriteLine("{0}+- Directory {1}", indentation, displayName); - - // Print all entries in the directory. - foreach (var entry in directory.Entries) - PrintResourceEntry(entry, indentationLevel + 1); - } - - private static void PrintResourcData(IResourceData data, int indentationLevel) - { - string indentation = new string(' ', indentationLevel * IndentationWidth); - - // Print the name of the data entry, as well as the size of the contents. - string displayName = data.Name ?? "ID: " + data.Id; - Console.WriteLine("{0}+- Data {1} ({2} bytes)", indentation, displayName, data.Contents.GetPhysicalSize()); - } diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 000000000..59f801047 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,5 @@ +- name: Articles + href: articles/ +- name: Api Documentation + href: api/ + homepage: api/index.md From 681861b4fb2b52854837a2b32f1775b819aaff80 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 10 May 2023 20:31:06 +0200 Subject: [PATCH 24/26] Avoid inconsistent sort by implementing a random shuffle instead of using Sort on NewGuid(). --- .../Builder/StringsStreamBufferTest.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/AsmResolver.DotNet.Tests/Builder/StringsStreamBufferTest.cs b/test/AsmResolver.DotNet.Tests/Builder/StringsStreamBufferTest.cs index 99940a373..70f31af7e 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/StringsStreamBufferTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/StringsStreamBufferTest.cs @@ -11,6 +11,19 @@ namespace AsmResolver.DotNet.Tests.Builder { public class StringsStreamBufferTest { + private static readonly Random Random = new(); + + private static void Shuffle(IList list) + { + int n = list.Count; + while (n > 1) + { + n--; + int k = Random.Next(n + 1); + (list[k], list[n]) = (list[n], list[k]); + } + } + [Fact] public void AddDistinct() { @@ -247,7 +260,7 @@ public void OptimizeMultipleLongChainsOfSuffixStrings() strings.Add(templateString1[i..]); for (int i = 0; i < templateString2.Length; i++) strings.Add(templateString2[i..]); - strings.Sort((_, _) => Guid.NewGuid().CompareTo(Guid.NewGuid())); + Shuffle(strings); var buffer = new StringsStreamBuffer(); foreach (string s in strings) From 2545e1a4374fd070e2b1f3667d7aa053c7caf3be Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 13 May 2023 17:05:03 +0200 Subject: [PATCH 25/26] Improve layout of API docs. Fix typos. --- docs/.gitignore | 3 +- docs/api/index.md | 4 + docs/api/toc.yml | 12 ++ docs/articles/index.md | 8 -- docs/articles/toc.yml | 93 ---------------- docs/docfx.json | 103 ++++++++++++++++-- docs/{articles => guides}/core/segments.md | 8 +- .../dotnet/advanced-module-reading.md | 0 .../dotnet/advanced-pe-image-building.md | 0 docs/{articles => guides}/dotnet/basics.md | 0 docs/{articles => guides}/dotnet/bundles.md | 0 docs/{articles => guides}/dotnet/cloning.md | 2 +- .../dotnet/dynamic-methods.md | 2 +- docs/{articles => guides}/dotnet/importing.md | 0 docs/{articles => guides}/dotnet/index.md | 0 .../dotnet/managed-method-bodies.md | 0 .../dotnet/managed-resources.md | 6 +- .../dotnet/member-tree.md | 19 ++-- .../dotnet/token-allocation.md | 0 .../dotnet/type-memory-layout.md | 0 .../dotnet/type-signatures.md | 0 .../dotnet/unmanaged-method-bodies.md | 0 docs/{articles => guides}/faq.md | 6 +- docs/{articles => guides}/overview.md | 0 docs/{articles => guides}/pdb/basics.md | 0 docs/{articles => guides}/pdb/index.md | 0 docs/{articles => guides}/pdb/symbols.md | 2 +- docs/{articles => guides}/pefile/basics.md | 0 docs/{articles => guides}/pefile/headers.md | 22 ++-- docs/{articles => guides}/pefile/index.md | 0 docs/{articles => guides}/pefile/sections.md | 8 +- .../peimage/advanced-pe-reading.md | 0 docs/{articles => guides}/peimage/basics.md | 0 docs/{articles => guides}/peimage/debug.md | 8 +- docs/{articles => guides}/peimage/dotnet.md | 10 +- .../peimage/exceptions.md | 10 +- docs/{articles => guides}/peimage/exports.md | 6 +- docs/{articles => guides}/peimage/imports.md | 8 +- docs/{articles => guides}/peimage/index.md | 0 .../peimage/pe-building.md | 0 docs/{articles => guides}/peimage/tls.md | 2 +- .../peimage/win32resources.md | 10 +- docs/guides/toc.yml | 85 +++++++++++++++ docs/index.md | 12 +- docs/toc.yml | 7 +- 45 files changed, 272 insertions(+), 184 deletions(-) create mode 100644 docs/api/index.md create mode 100644 docs/api/toc.yml delete mode 100644 docs/articles/index.md delete mode 100644 docs/articles/toc.yml rename docs/{articles => guides}/core/segments.md (96%) rename docs/{articles => guides}/dotnet/advanced-module-reading.md (100%) rename docs/{articles => guides}/dotnet/advanced-pe-image-building.md (100%) rename docs/{articles => guides}/dotnet/basics.md (100%) rename docs/{articles => guides}/dotnet/bundles.md (100%) rename docs/{articles => guides}/dotnet/cloning.md (98%) rename docs/{articles => guides}/dotnet/dynamic-methods.md (96%) rename docs/{articles => guides}/dotnet/importing.md (100%) rename docs/{articles => guides}/dotnet/index.md (100%) rename docs/{articles => guides}/dotnet/managed-method-bodies.md (100%) rename docs/{articles => guides}/dotnet/managed-resources.md (98%) rename docs/{articles => guides}/dotnet/member-tree.md (80%) rename docs/{articles => guides}/dotnet/token-allocation.md (100%) rename docs/{articles => guides}/dotnet/type-memory-layout.md (100%) rename docs/{articles => guides}/dotnet/type-signatures.md (100%) rename docs/{articles => guides}/dotnet/unmanaged-method-bodies.md (100%) rename docs/{articles => guides}/faq.md (92%) rename docs/{articles => guides}/overview.md (100%) rename docs/{articles => guides}/pdb/basics.md (100%) rename docs/{articles => guides}/pdb/index.md (100%) rename docs/{articles => guides}/pdb/symbols.md (98%) rename docs/{articles => guides}/pefile/basics.md (100%) rename docs/{articles => guides}/pefile/headers.md (89%) rename docs/{articles => guides}/pefile/index.md (100%) rename docs/{articles => guides}/pefile/sections.md (93%) rename docs/{articles => guides}/peimage/advanced-pe-reading.md (100%) rename docs/{articles => guides}/peimage/basics.md (100%) rename docs/{articles => guides}/peimage/debug.md (88%) rename docs/{articles => guides}/peimage/dotnet.md (97%) rename docs/{articles => guides}/peimage/exceptions.md (85%) rename docs/{articles => guides}/peimage/exports.md (88%) rename docs/{articles => guides}/peimage/imports.md (88%) rename docs/{articles => guides}/peimage/index.md (100%) rename docs/{articles => guides}/peimage/pe-building.md (100%) rename docs/{articles => guides}/peimage/tls.md (96%) rename docs/{articles => guides}/peimage/win32resources.md (92%) create mode 100644 docs/guides/toc.yml diff --git a/docs/.gitignore b/docs/.gitignore index f6cdee1d7..c81988ad8 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -7,4 +7,5 @@ /**/bin/ /**/obj/ _site/ -api/ +api/*/*.yml +api/*/*.manifest diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 000000000..27e5cc4e5 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,4 @@ +# AsmResolver API Reference + +This is the full API reference of the AsmResolver project. + diff --git a/docs/api/toc.yml b/docs/api/toc.yml new file mode 100644 index 000000000..60b5b3da6 --- /dev/null +++ b/docs/api/toc.yml @@ -0,0 +1,12 @@ +- name: AsmResolver.dll + href: core/toc.yml +- name: AsmResolver.PE.File.dll + href: pe-file/toc.yml +- name: AsmResolver.PE.dll + href: pe/toc.yml +- name: AsmResolver.PE.Win32Resources.dll + href: win32resources/toc.yml +- name: AsmResolver.DotNet.dll + href: dotnet/toc.yml +- name: AsmResolver.Symbols.Pdb.dll + href: symbols/toc.yml \ No newline at end of file diff --git a/docs/articles/index.md b/docs/articles/index.md deleted file mode 100644 index 851ee02d7..000000000 --- a/docs/articles/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# AsmResolver - -This is the documentation of the AsmResolver project. AsmResolver is a -set of libraries allowing .NET programmers to read, modify and write -executable files. This includes .NET as well as native images. The -library exposes high-level representations of the PE, while still -allowing the user to access low-level structures. - diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml deleted file mode 100644 index 8e07653be..000000000 --- a/docs/articles/toc.yml +++ /dev/null @@ -1,93 +0,0 @@ -items: -- name: Introduction - href: index.md - -- name: General - items: - - name: API Overview - href: overview.md - - name: FAQ - href: faq.md - -- name: Core API - items: - - name: Reading and Writing File Segments - href: core/segments.md - -- name: PE Files - items: - - name: Overview - href: pefile/index.md - - name: Basic I/O - href: pefile/basics.md - - name: PE Headers - href: pefile/headers.md - - name: PE Sections - href: pefile/sections.md - -- name: PE Images - items: - - name: Overview - href: peimage/index.md - - name: Basic I/O - href: peimage/basics.md - - name: Advanced PE Image Reading - href: peimage/advanced-pe-reading.md - - name: Imports Directory - href: peimage/imports.md - - name: Exports Directory - href: peimage/exports.md - - name: Win32 Resources - href: peimage/win32resources.md - - name: Exceptions Directory - href: peimage/exceptions.md - - name: Debug Directory - href: peimage/debug.md - - name: TLS Directory - href: peimage/tls.md - - name: .NET Data Directories - href: peimage/dotnet.md - - name: PE File Building - href: peimage/pe-building.md - -- name: .NET assemblies and modules - items: - - name: Overview - href: dotnet/index.md - - name: Basic I/O - href: dotnet/basics.md - - name: Advanced Module Reading - href: dotnet/advanced-module-reading.md - - name: The Member Tree - href: dotnet/member-tree.md - - name: Type Signatures - href: dotnet/type-signatures.md - - name: Reference Importing - href: dotnet/importing.md - - name: CIL Method Bodies - href: dotnet/managed-method-bodies.md - - name: Native Method Bodies - href: dotnet/unmanaged-method-bodies.md - - name: Dynamic Methods - href: dotnet/dynamic-methods.md - - name: Managed Resources - href: dotnet/managed-resources.md - - name: Member Cloning - href: dotnet/cloning.md - - name: Metadata Token Allocation - href: dotnet/token-allocation.md - - name: Type Memory Layout - href: dotnet/type-memory-layout.md - - name: AppHost / SingleFileHost Bundles - href: dotnet/bundles.md - - name: Advanced PE Image Building - href: dotnet/advanced-pe-image-building.md - -- name: PDB Symbols - items: - - name: Overview - href: pdb/index.md - - name: Basic I/O - href: pdb/basics.md - - name: Symbols - href: pdb/symbols.md diff --git a/docs/docfx.json b/docs/docfx.json index c2b9a48af..d1b61fc17 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -3,19 +3,97 @@ { "src": [ { - "files": [ - "**/*.csproj" - ], + "files": [ "AsmResolver/*.csproj" ], "src": "../src" } ], - "dest": "api", + "dest": "api/core", "includePrivateMembers": false, "disableGitFeatures": false, "disableDefaultFilter": false, "noRestore": false, "namespaceLayout": "flattened", - "memberLayout": "separatePages", + "memberLayout": "samePage", + "allowCompilationErrors": false + }, + { + "src": [ + { + "files": [ "AsmResolver.PE.File/*.csproj" ], + "src": "../src" + } + ], + "dest": "api/pe-file", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "allowCompilationErrors": false + }, + { + "src": [ + { + "files": [ "AsmResolver.PE/*.csproj" ], + "src": "../src" + } + ], + "dest": "api/pe", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "allowCompilationErrors": false + }, + { + "src": [ + { + "files": [ "AsmResolver.DotNet/*.csproj", "AsmResolver.DotNet.Dynamic/*.csproj" ], + "src": "../src" + } + ], + "dest": "api/dotnet", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "allowCompilationErrors": false + }, + { + "src": [ + { + "files": [ "AsmResolver.Symbols.Pdb/*.csproj" ], + "src": "../src" + } + ], + "dest": "api/symbols", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "allowCompilationErrors": false + }, + { + "src": [ + { + "files": [ "AsmResolver.PE.Win32Resources/*.csproj" ], + "src": "../src" + } + ], + "dest": "api/win32resources", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", "allowCompilationErrors": false } ], @@ -23,14 +101,14 @@ "content": [ { "files": [ - "api/**.yml", + "api/**/*.yml", "api/index.md" ] }, { "files": [ - "articles/**.md", - "articles/**/toc.yml", + "guides/**.md", + "guides/**/toc.yml", "toc.yml", "*.md" ] @@ -51,7 +129,14 @@ "modern", "my-template" ], - "postProcessors": [], + "postProcessors": [ + "ExtractSearchIndex" + ], + "globalMetadata": { + "_enableSearch": "true", + "_appTitle": "AsmResolver", + "_appName": "AsmResolver" + }, "keepFileLink": false, "disableGitFeatures": false } diff --git a/docs/articles/core/segments.md b/docs/guides/core/segments.md similarity index 96% rename from docs/articles/core/segments.md rename to docs/guides/core/segments.md index 4473c31d3..78eab303f 100644 --- a/docs/articles/core/segments.md +++ b/docs/guides/core/segments.md @@ -185,8 +185,8 @@ ISegment segment = ... // Relocate a segment to an offsets-rva pair: segment.UpdateOffsets(new RelocationParameters(offset: 0x200, rva: 0x2000); -Console.WriteLine("Offset: 0x{0:X8}", segment.Offset); // Prints 0x200 -Console.WriteLine("Rva: 0x{0:X8}", segment.Rva); // Prints 0x2000 +Console.WriteLine($"Offset: 0x{segment.Offset:X8}"); // Prints 0x200 +Console.WriteLine($"Rva: 0x{segment.Rva:X8}"); // Prints 0x2000 ``` > [!WARNING] @@ -207,8 +207,8 @@ ISegment segment = ... uint physicalSize = segment.GetPhysicalSize(); uint virtualSize = segment.GetVirtualSize(); -Console.WriteLine("Physical (File) Size: 0x{0:X8}", physicalSize); -Console.WriteLine("Virtual (Runtime) Size: 0x{0:X8}", virtualSize); +Console.WriteLine($"Physical (File) Size: 0x{physicalSize:X8}"); +Console.WriteLine($"Virtual (Runtime) Size: 0x{virtualSize:X8}"); ``` > [!WARNING] diff --git a/docs/articles/dotnet/advanced-module-reading.md b/docs/guides/dotnet/advanced-module-reading.md similarity index 100% rename from docs/articles/dotnet/advanced-module-reading.md rename to docs/guides/dotnet/advanced-module-reading.md diff --git a/docs/articles/dotnet/advanced-pe-image-building.md b/docs/guides/dotnet/advanced-pe-image-building.md similarity index 100% rename from docs/articles/dotnet/advanced-pe-image-building.md rename to docs/guides/dotnet/advanced-pe-image-building.md diff --git a/docs/articles/dotnet/basics.md b/docs/guides/dotnet/basics.md similarity index 100% rename from docs/articles/dotnet/basics.md rename to docs/guides/dotnet/basics.md diff --git a/docs/articles/dotnet/bundles.md b/docs/guides/dotnet/bundles.md similarity index 100% rename from docs/articles/dotnet/bundles.md rename to docs/guides/dotnet/bundles.md diff --git a/docs/articles/dotnet/cloning.md b/docs/guides/dotnet/cloning.md similarity index 98% rename from docs/articles/dotnet/cloning.md rename to docs/guides/dotnet/cloning.md index f197539a9..4ec2d8e7c 100644 --- a/docs/articles/dotnet/cloning.md +++ b/docs/guides/dotnet/cloning.md @@ -201,7 +201,7 @@ 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 [Common Caveats using the Importer](/articles/dotnet/importing.html#common-caveats-using-the-importer) +See [Common Caveats using the Importer](/guides/dotnet/importing.html#common-caveats-using-the-importer) for more information on reference importing and its caveats. ## Post-processing of cloned members diff --git a/docs/articles/dotnet/dynamic-methods.md b/docs/guides/dotnet/dynamic-methods.md similarity index 96% rename from docs/articles/dotnet/dynamic-methods.md rename to docs/guides/dotnet/dynamic-methods.md index 6dc0a07bc..290fc4d12 100644 --- a/docs/articles/dotnet/dynamic-methods.md +++ b/docs/guides/dotnet/dynamic-methods.md @@ -67,6 +67,6 @@ contextModule.GetOrCreateModuleType().Methods.Add(definition); contextModule.Write("Program.patched.dll"); ``` -See [Obtaining methods and fields](/articles/dotnet/member-tree.html#obtaining-methods-and-fields) +See [Obtaining methods and fields](/guides/dotnet/member-tree.html#obtaining-methods-and-fields) and [CIL Method Bodies](managed-method-bodies.md) for more information on how to use `MethodDefinition` objects. diff --git a/docs/articles/dotnet/importing.md b/docs/guides/dotnet/importing.md similarity index 100% rename from docs/articles/dotnet/importing.md rename to docs/guides/dotnet/importing.md diff --git a/docs/articles/dotnet/index.md b/docs/guides/dotnet/index.md similarity index 100% rename from docs/articles/dotnet/index.md rename to docs/guides/dotnet/index.md diff --git a/docs/articles/dotnet/managed-method-bodies.md b/docs/guides/dotnet/managed-method-bodies.md similarity index 100% rename from docs/articles/dotnet/managed-method-bodies.md rename to docs/guides/dotnet/managed-method-bodies.md diff --git a/docs/articles/dotnet/managed-resources.md b/docs/guides/dotnet/managed-resources.md similarity index 98% rename from docs/articles/dotnet/managed-resources.md rename to docs/guides/dotnet/managed-resources.md index 607492851..49cc1eb9b 100644 --- a/docs/articles/dotnet/managed-resources.md +++ b/docs/guides/dotnet/managed-resources.md @@ -191,9 +191,9 @@ includes the name, the type of the resource and the deserialized data: ``` csharp foreach (var entry in set) { - Console.WriteLine("Name: " + entry.Name); - Console.WriteLine("Type: " + entry.Type.FullName); - Console.WriteLine("Data: " + entry.Data); + Console.WriteLine($"Name: {entry.Name}"); + Console.WriteLine($"Type: {entry.Type.FullName}"); + Console.WriteLine($"Data: {entry.Data}"); } ``` diff --git a/docs/articles/dotnet/member-tree.md b/docs/guides/dotnet/member-tree.md similarity index 80% rename from docs/articles/dotnet/member-tree.md rename to docs/guides/dotnet/member-tree.md index a91f64271..ec0e7a972 100644 --- a/docs/articles/dotnet/member-tree.md +++ b/docs/guides/dotnet/member-tree.md @@ -47,7 +47,7 @@ private static void DumpTypes(IEnumerable types, int indentation foreach (var type in types) { // Print the name of the current type. - Console.WriteLine("{0}- {1} : {2:X8}", indentation, type.Name, type.MetadataToken.ToInt32()); + Console.WriteLine($"{indentation}- {type.Name} : {type.MetadataToken}"); // Dump any nested types. DumpTypes(type.NestedTypes, indentationLevel + 1); @@ -62,12 +62,12 @@ that the type defines: ``` csharp foreach (var method in type.Methods) - Console.WriteLine("{0} : {1:X8}", method.Name, method.MetadataToken.ToInt32()); + Console.WriteLine($"{method.Name} : {method.MetadataToken}"); ``` ``` csharp foreach (var field in type.Fields) - Console.WriteLine("{0} : {1:X8}", field.Name, field.MetadataToken.ToInt32()); + Console.WriteLine($"{field.Name} : {field.MetadataToken}"); ``` Methods and fields have a `Signature` property, that contain the return @@ -75,13 +75,13 @@ and parameter types, or the field type respectively. ``` csharp MethodDefinition method = ... -Console.WriteLine("Return type: " + method.Signature.ReturnType); -Console.WriteLine("Parameter types: " + string.Join(", ", method.Signature.ParameterTypes)); +Console.WriteLine($"Return type: {method.Signature.ReturnType}"); +Console.WriteLine($"Parameter types: {string.Join(", ", method.Signature.ParameterTypes)}"); ``` ``` csharp FieldDefinition field = ... -Console.WriteLine("Field type: " + field.Signature.FieldType); +Console.WriteLine($"Field type: {field.Signature.FieldType}"); ``` However, for reading parameters from a method definition, it is @@ -103,12 +103,12 @@ fields; `TypeDefinition` exposes them in a list as well: ``` csharp foreach (var @event in type.Events) - Console.WriteLine("{0} : {1:X8}", @event.Name, @event.MetadataToken.ToInt32()); + Console.WriteLine($"{@event.Name} : {@event.MetadataToken}"); ``` ``` csharp foreach (var property in type.Properties) - Console.WriteLine("{0} : {1:X8}", property.Name, property.MetadataToken.ToInt32()); + Console.WriteLine($"{property.Name} : {property.MetadataToken}"); ``` Properties and events have methods associated to them. These are @@ -117,7 +117,6 @@ accessible through the `Semantics` property: ``` csharp foreach (MethodSemantics semantic in property.Semantics) { - Console.WriteLine("{0} {1} : {2:X8}", semantic.Attributes, semantic.Method.Name, - semantic.MetadataToken.ToInt32()); + Console.WriteLine($"{semantic.Attributes} {semantic.Method.Name} : {semantic.MetadataToken}"); } ``` diff --git a/docs/articles/dotnet/token-allocation.md b/docs/guides/dotnet/token-allocation.md similarity index 100% rename from docs/articles/dotnet/token-allocation.md rename to docs/guides/dotnet/token-allocation.md diff --git a/docs/articles/dotnet/type-memory-layout.md b/docs/guides/dotnet/type-memory-layout.md similarity index 100% rename from docs/articles/dotnet/type-memory-layout.md rename to docs/guides/dotnet/type-memory-layout.md diff --git a/docs/articles/dotnet/type-signatures.md b/docs/guides/dotnet/type-signatures.md similarity index 100% rename from docs/articles/dotnet/type-signatures.md rename to docs/guides/dotnet/type-signatures.md diff --git a/docs/articles/dotnet/unmanaged-method-bodies.md b/docs/guides/dotnet/unmanaged-method-bodies.md similarity index 100% rename from docs/articles/dotnet/unmanaged-method-bodies.md rename to docs/guides/dotnet/unmanaged-method-bodies.md diff --git a/docs/articles/faq.md b/docs/guides/faq.md similarity index 92% rename from docs/articles/faq.md rename to docs/guides/faq.md index 4e4806429..dddf2448c 100644 --- a/docs/articles/faq.md +++ b/docs/guides/faq.md @@ -25,9 +25,9 @@ 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](/articles/peimage/advanced-pe-reading.html#custom-error-handling) (PE) -or [Advanced Module Reading](/articles/dotnet/advanced-module-reading.html#pe-image-reading-parameters) (.NET), -and [Image Builder Diagnostics](/articles/dotnet/advanced-pe-image-building.html#image-builder-diagnostics) (.NET). +[Advanced PE Image Reading](/guides/peimage/advanced-pe-reading.html#custom-error-handling) (PE) +or [Advanced Module Reading](/guides/dotnet/advanced-module-reading.html#pe-image-reading-parameters) (.NET), +and [Image Builder Diagnostics](/guides/dotnet/advanced-pe-image-building.html#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. diff --git a/docs/articles/overview.md b/docs/guides/overview.md similarity index 100% rename from docs/articles/overview.md rename to docs/guides/overview.md diff --git a/docs/articles/pdb/basics.md b/docs/guides/pdb/basics.md similarity index 100% rename from docs/articles/pdb/basics.md rename to docs/guides/pdb/basics.md diff --git a/docs/articles/pdb/index.md b/docs/guides/pdb/index.md similarity index 100% rename from docs/articles/pdb/index.md rename to docs/guides/pdb/index.md diff --git a/docs/articles/pdb/symbols.md b/docs/guides/pdb/symbols.md similarity index 98% rename from docs/articles/pdb/symbols.md rename to docs/guides/pdb/symbols.md index 444a4332f..6235b3c2f 100644 --- a/docs/articles/pdb/symbols.md +++ b/docs/guides/pdb/symbols.md @@ -75,7 +75,7 @@ foreach (var module in image.Modules) { Console.WriteLine(module.Name); foreach (var symbol in image.Symbols) - Console.WriteLine("\t- {0}", symbol); + Console.WriteLine($"\t- {symbol}"); Console.WriteLine(); } ``` diff --git a/docs/articles/pefile/basics.md b/docs/guides/pefile/basics.md similarity index 100% rename from docs/articles/pefile/basics.md rename to docs/guides/pefile/basics.md diff --git a/docs/articles/pefile/headers.md b/docs/guides/pefile/headers.md similarity index 89% rename from docs/articles/pefile/headers.md rename to docs/guides/pefile/headers.md index 189394e2b..bb51ae70d 100644 --- a/docs/articles/pefile/headers.md +++ b/docs/guides/pefile/headers.md @@ -27,7 +27,7 @@ and changing this offset if desired: PEFile file = ... // Obtain e_lfanew: -Console.WriteLine("e_flanew: {0:X8}", file.DosHeader.NextHeaderOffset); +Console.WriteLine($"e_flanew: {file.DosHeader.NextHeaderOffset:X8}"); // Set a new e_lfanew: file.DosHeader.NextHeaderOffset = 0x100; @@ -49,13 +49,13 @@ readable and writeable: PEFile file = ... FileHeader header = file.FileHeader; -Console.WriteLine($"Machine: {header.Machine}"); -Console.WriteLine("NumberOfSections: {header.NumberOfSections}"); -Console.WriteLine("TimeDateStamp: 0x{header.TimeDateStamp:X8}"); -Console.WriteLine("PointerToSymbolTable: 0x{header.PointerToSymbolTable:X8}"); -Console.WriteLine("NumberOfSymbols: {header.NumberOfSymbols}"); -Console.WriteLine("SizeOfOptionalHeader: 0x{header.SizeOfOptionalHeader:X4}"); -Console.WriteLine("Characteristics: {header.Characteristics}"); +Console.WriteLine($"Machine: {header.Machine}"); +Console.WriteLine($"NumberOfSections: {header.NumberOfSections}"); +Console.WriteLine($"TimeDateStamp: 0x{header.TimeDateStamp:X8}"); +Console.WriteLine($"PointerToSymbolTable: 0x{header.PointerToSymbolTable:X8}"); +Console.WriteLine($"NumberOfSymbols: {header.NumberOfSymbols}"); +Console.WriteLine($"SizeOfOptionalHeader: 0x{header.SizeOfOptionalHeader:X4}"); +Console.WriteLine($"Characteristics: {header.Characteristics}"); ``` > [!NOTE] @@ -175,7 +175,7 @@ applications requiring a console window. ``` csharp // Reading the target sub system: -Console.WriteLine("SubSystem: {header.SubSystem}"); +Console.WriteLine($"SubSystem: {header.SubSystem}"); // Changing the application to a GUI application: header.SubSystem = SubSystem.WindowsGui; @@ -188,8 +188,8 @@ The optional header defines two properties `FileAlignment` and disk and in memory at runtime respectively. ``` csharp -Console.WriteLine("FileAlignment: 0x{header.FileAlignment}"); -Console.WriteLine("SectionAlignment: 0x{header.SectionAlignment}"); +Console.WriteLine($"FileAlignment: 0x{header.FileAlignment}"); +Console.WriteLine($"SectionAlignment: 0x{header.SectionAlignment}"); ``` AsmResolver respects the value in `FileAlignment` when writing a diff --git a/docs/articles/pefile/index.md b/docs/guides/pefile/index.md similarity index 100% rename from docs/articles/pefile/index.md rename to docs/guides/pefile/index.md diff --git a/docs/articles/pefile/sections.md b/docs/guides/pefile/sections.md similarity index 93% rename from docs/articles/pefile/sections.md rename to docs/guides/pefile/sections.md index 6734a3016..d8ddf9530 100644 --- a/docs/articles/pefile/sections.md +++ b/docs/guides/pefile/sections.md @@ -76,7 +76,7 @@ file.Sections.Add(section); ``` For more advanced section building, see -[Building Sections](/articles/peimage/pe-building.html#building-sections) +[Building Sections](/guides/peimage/pe-building.html#building-sections) [Reading and Writing File Segments](../core/segments.md). ## Updating Section Offsets @@ -92,7 +92,7 @@ file.Sections.Add(section); file.AlignSections(); -Console.WriteLine("New section RVA: 0x{section.Rva:X8}"); +Console.WriteLine($"New section RVA: 0x{section.Rva:X8}"); ``` If you want to align the sections and also automatically update the @@ -105,8 +105,8 @@ file.Sections.Add(section); file.UpdateHeaders(); -Console.WriteLine("New section RVA: 0x{section.Rva:X8}"); -Console.WriteLine("New section count: {file.FileHeader.NumberOfSections}"); +Console.WriteLine($"New section RVA: 0x{section.Rva:X8}"); +Console.WriteLine($"New section count: {file.FileHeader.NumberOfSections}"); ``` > [!NOTE] diff --git a/docs/articles/peimage/advanced-pe-reading.md b/docs/guides/peimage/advanced-pe-reading.md similarity index 100% rename from docs/articles/peimage/advanced-pe-reading.md rename to docs/guides/peimage/advanced-pe-reading.md diff --git a/docs/articles/peimage/basics.md b/docs/guides/peimage/basics.md similarity index 100% rename from docs/articles/peimage/basics.md rename to docs/guides/peimage/basics.md diff --git a/docs/articles/peimage/debug.md b/docs/guides/peimage/debug.md similarity index 88% rename from docs/articles/peimage/debug.md rename to docs/guides/peimage/debug.md index 5d24f3bbb..a29f82d8c 100644 --- a/docs/articles/peimage/debug.md +++ b/docs/guides/peimage/debug.md @@ -21,9 +21,9 @@ that is stored. ``` csharp foreach (var entry in image.DebugData) { - Console.WriteLine("Debug Data Type: {0}", entry.Contents.Type); - Console.WriteLine("Version: {0}.{1}", entry.MajorVersion, entry.MinorVersion); - Console.WriteLine("Data start: {0:X8}", entry.Contents.Rva); + Console.WriteLine($"Debug Data Type: {entry.Contents.Type}"); + Console.WriteLine($"Version: {entry.MajorVersion}.{entry.MinorVersion}"); + Console.WriteLine($"Data start: {entry.Contents.Rva:X8}"); } ``` @@ -64,6 +64,6 @@ Debug Database (`*.pdb`) file that is associated to the image. if (codeViewData.Signature == CodeViewSignature.Rsds) { var rsdsData = (RsdsDataSegment) data; - Console.WriteLine("PDB Path: {0}", rsdsData.Path); + Console.WriteLine($"PDB Path: {rsdsData.Path}"); } ``` diff --git a/docs/articles/peimage/dotnet.md b/docs/guides/peimage/dotnet.md similarity index 97% rename from docs/articles/peimage/dotnet.md rename to docs/guides/peimage/dotnet.md index 351df45a0..fd491a183 100644 --- a/docs/articles/peimage/dotnet.md +++ b/docs/guides/peimage/dotnet.md @@ -16,7 +16,7 @@ The .NET data directory can be accessed by the ``` csharp IPEImage peImage = ... -Console.WriteLine("Managed entry point: {0:X8}", peImage.DotNetDirectory.EntryPoint); +Console.WriteLine($"Managed entry point: {peImage.DotNetDirectory.EntryPoint}"); ``` ## Metadata directory @@ -33,8 +33,8 @@ interface: ``` csharp var metadata = peImage.DotNetDirectory.Metadata; -Console.WriteLine("Metadata file format version: {0}.{1}", metadata.MajorVersion, metadata.MinorVersion); -Console.WriteLine("Target .NET runtime version: " + metadata.VersionString); +Console.WriteLine($"Metadata file format version: {metadata.MajorVersion}.{metadata.MinorVersion}"); +Console.WriteLine($"Target .NET runtime version: {metadata.VersionString}"); ``` ## Metadata streams @@ -44,7 +44,7 @@ The `IMetadata` interface also exposes the `Streams` property, a list of ``` csharp foreach (var stream in metadata.Streams) - Console.WriteLine("Name: " + stream.Name); + Console.WriteLine($"Name: {stream.Name}"); ``` Alternatively, it is possible to get a stream by its name using the @@ -223,7 +223,7 @@ all row definitions: Metadata tables are similar to normal`ICollection\` instances. They provide enumerators, indexers and methods to add or remove rows from the table. ``` csharp -Console.WriteLine("Number of types: " + typeDefTable.Count); / +Console.WriteLine($"Number of types: {typeDefTable.Count}"); // Get a single row. TypeDefinitionRow firstTypeRow = typeDefTable[0]; diff --git a/docs/articles/peimage/exceptions.md b/docs/guides/peimage/exceptions.md similarity index 85% rename from docs/articles/peimage/exceptions.md rename to docs/guides/peimage/exceptions.md index 348bb9768..b7459d536 100644 --- a/docs/articles/peimage/exceptions.md +++ b/docs/guides/peimage/exceptions.md @@ -24,8 +24,8 @@ method, in a platform agnostic manner. ``` csharp foreach (var function in peImage.Exceptions.GetEntries()) { - Console.WriteLine("Begin: {0:X8}.", function.Begin.Rva); - Console.WriteLine("End: {0:X8}.", function.End.Rva); + Console.WriteLine($"Begin: {function.Begin.Rva:X8}."); + Console.WriteLine($"End: {function.End.Rva:X8}."); } ``` @@ -49,13 +49,13 @@ var directory = (X64ExceptionsDirectory) peImage.Exceptions; foreach (var function in directory.Entries) { - Console.WriteLine("Begin: {0:X8}.", function.Begin.Rva); - Console.WriteLine("End: {0:X8}.", function.End.Rva); + Console.WriteLine($"Begin: {function.Begin.Rva:X8}."); + Console.WriteLine($"End: {function.End.Rva:X8}."); var unwindInfo = function.UnwindInfo; // Get handler start. - Console.WriteLine("Handler Start: {0:X8}.", unwindInfo.ExceptionHandler.Rva); + Console.WriteLine($"Handler Start: {unwindInfo.ExceptionHandler.Rva:X8}."); // Read custom SEH data associated to this unwind information. var dataReader = function.ExceptionHandlerData.CreateReader(); diff --git a/docs/articles/peimage/exports.md b/docs/guides/peimage/exports.md similarity index 88% rename from docs/articles/peimage/exports.md rename to docs/guides/peimage/exports.md index dc5600258..3d70c6cd2 100644 --- a/docs/articles/peimage/exports.md +++ b/docs/guides/peimage/exports.md @@ -38,9 +38,9 @@ given `IPEImage` instance: ``` csharp foreach (var symbol in peImage.Exports.Entries) { - Console.WriteLine("Ordinal: " + symbol.Ordinal); + Console.WriteLine($"Ordinal: {symbol.Ordinal}"); if (symbol.IsByName) - Console.WriteLine("Name: " + symbol.Name); - Console.WriteLine("Address: " + symbol.Address.Rva.ToString("X8")); + Console.WriteLine($"Name: {symbol.Name}"); + Console.WriteLine($"Address: {symbol.Address.Rva:X8}"); } ``` diff --git a/docs/articles/peimage/imports.md b/docs/guides/peimage/imports.md similarity index 88% rename from docs/articles/peimage/imports.md rename to docs/guides/peimage/imports.md index 77fa00d36..6f7d28cd7 100644 --- a/docs/articles/peimage/imports.md +++ b/docs/guides/peimage/imports.md @@ -22,14 +22,14 @@ a given `IPEImage` instance: ``` csharp foreach (var module in peImage.Imports) { - Console.WriteLine("Module: " + module.Name); + Console.WriteLine($"Module: {module.Name}"); foreach (var member in module.Symbols) { if (member.IsImportByName) - Console.WriteLine("\t- " + member.Name); + Console.WriteLine($"\t- {member.Name}"); else - Console.WriteLine("\t- #" + member.Ordinal); + Console.WriteLine($"\t- #{member.Ordinal}"); } Console.WriteLine(); @@ -78,4 +78,4 @@ While the Import Hash can be a good identifier for native PE images, for .NET images this is not the case. .NET images usually only import a single external symbol (either `mscoree.dll!_CorExeMain` or `mscoree.dll!_CorDllMain`), and as such they will almost always have the -exact same Import Hash. See [TypeReference Hash (TRH)](/articles/peimage/dotnet.html#typereference-hash-trh) for an alternative for .NET images. +exact same Import Hash. See [TypeReference Hash (TRH)](/guides/peimage/dotnet.html#typereference-hash-trh) for an alternative for .NET images. diff --git a/docs/articles/peimage/index.md b/docs/guides/peimage/index.md similarity index 100% rename from docs/articles/peimage/index.md rename to docs/guides/peimage/index.md diff --git a/docs/articles/peimage/pe-building.md b/docs/guides/peimage/pe-building.md similarity index 100% rename from docs/articles/peimage/pe-building.md rename to docs/guides/peimage/pe-building.md diff --git a/docs/articles/peimage/tls.md b/docs/guides/peimage/tls.md similarity index 96% rename from docs/articles/peimage/tls.md rename to docs/guides/peimage/tls.md index 6b6cec285..664a59b96 100644 --- a/docs/articles/peimage/tls.md +++ b/docs/guides/peimage/tls.md @@ -49,7 +49,7 @@ of every TLS callback function. ``` csharp for (int i = 0; i < directory.CallbackFunctions.Count; i++) { - Console.WriteLine("TLS Callback #{0}: {1:X8}", directory.CallbackFunctions.Rva); + Console.WriteLine($"TLS Callback #{i} : {directory.CallbackFunctions.Rva:X8}"); } ``` diff --git a/docs/articles/peimage/win32resources.md b/docs/guides/peimage/win32resources.md similarity index 92% rename from docs/articles/peimage/win32resources.md rename to docs/guides/peimage/win32resources.md index 4fa0193dc..4df997083 100644 --- a/docs/articles/peimage/win32resources.md +++ b/docs/guides/peimage/win32resources.md @@ -25,9 +25,9 @@ IResourceDirectory root = image.Resources; foreach (var entry in root.Entries) { if (entry.IsDirectory) - Console.WriteLine("Directory {0}.", entry.Id); + Console.WriteLine($"Directory {entry.Id}."); else // if (entry.IsData) - Console.WriteLine("Data {0}.", entry.Id); + Console.WriteLine($"Data {entry.Id}."); } ``` @@ -135,7 +135,7 @@ private static void PrintResourceDirectory(IResourceDirectory directory, int ind // Print the name or ID of the directory. string displayName = directory.Name ?? "ID: " + directory.Id; - Console.WriteLine("{0}+- Directory {1}", indentation, displayName); + Console.WriteLine($"{indentation}+- Directory {displayName}"); // Print all entries in the directory. foreach (var entry in directory.Entries) @@ -147,7 +147,7 @@ private static void PrintResourcData(IResourceData data, int indentationLevel) string indentation = new string(' ', indentationLevel * IndentationWidth); // Print the name of the data entry, as well as the size of the contents. - string displayName = data.Name ?? "ID: " + data.Id; - Console.WriteLine("{0}+- Data {1} ({2} bytes)", indentation, displayName, data.Contents.GetPhysicalSize()); + string displayName = data.Name ?? $"ID: {data.Id}"; + Console.WriteLine($"{indentation}+- Data {displayName} ({data.Contents.GetPhysicalSize()} bytes)"); } ``` diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml new file mode 100644 index 000000000..ff55f16ac --- /dev/null +++ b/docs/guides/toc.yml @@ -0,0 +1,85 @@ +- name: General +- name: Introduction + href: ../index.md +- name: API Overview + href: overview.md +- name: FAQ + href: faq.md + +- name: Core API +- name: Reading and Writing File Segments + href: core/segments.md + +- name: PE Files +- name: Overview + href: pefile/index.md +- name: Basic I/O + href: pefile/basics.md +- name: PE Headers + href: pefile/headers.md +- name: PE Sections + href: pefile/sections.md + +- name: PE Images +- name: Overview + href: peimage/index.md +- name: Basic I/O + href: peimage/basics.md +- name: Advanced PE Image Reading + href: peimage/advanced-pe-reading.md +- name: Imports Directory + href: peimage/imports.md +- name: Exports Directory + href: peimage/exports.md +- name: Win32 Resources + href: peimage/win32resources.md +- name: Exceptions Directory + href: peimage/exceptions.md +- name: Debug Directory + href: peimage/debug.md +- name: TLS Directory + href: peimage/tls.md +- name: .NET Data Directories + href: peimage/dotnet.md +- name: PE File Building + href: peimage/pe-building.md + +- name: .NET assemblies and modules +- name: Overview + href: dotnet/index.md +- name: Basic I/O + href: dotnet/basics.md +- name: Advanced Module Reading + href: dotnet/advanced-module-reading.md +- name: The Member Tree + href: dotnet/member-tree.md +- name: Type Signatures + href: dotnet/type-signatures.md +- name: Reference Importing + href: dotnet/importing.md +- name: CIL Method Bodies + href: dotnet/managed-method-bodies.md +- name: Native Method Bodies + href: dotnet/unmanaged-method-bodies.md +- name: Dynamic Methods + href: dotnet/dynamic-methods.md +- name: Managed Resources + href: dotnet/managed-resources.md +- name: Member Cloning + href: dotnet/cloning.md +- name: Metadata Token Allocation + href: dotnet/token-allocation.md +- name: Type Memory Layout + href: dotnet/type-memory-layout.md +- name: AppHost / SingleFileHost Bundles + href: dotnet/bundles.md +- name: Advanced PE Image Building + href: dotnet/advanced-pe-image-building.md + +- name: PDB Symbols +- name: Overview + href: pdb/index.md +- name: Basic I/O + href: pdb/basics.md +- name: Symbols + href: pdb/symbols.md diff --git a/docs/index.md b/docs/index.md index 3ae250636..851ee02d7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,8 @@ -# This is the **HOMEPAGE**. -Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. -## Quick Start Notes: -1. Add images to the *images* folder if the file is referencing an image. +# AsmResolver + +This is the documentation of the AsmResolver project. AsmResolver is a +set of libraries allowing .NET programmers to read, modify and write +executable files. This includes .NET as well as native images. The +library exposes high-level representations of the PE, while still +allowing the user to access low-level structures. + diff --git a/docs/toc.yml b/docs/toc.yml index 59f801047..2fd452595 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,5 +1,4 @@ -- name: Articles - href: articles/ -- name: Api Documentation +- name: Guides + href: guides/ +- name: API href: api/ - homepage: api/index.md From 11b6af9e383a196db2b25aff924ca7debd5eaec2 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 13 May 2023 17:09:55 +0200 Subject: [PATCH 26/26] Make README more concise and update docs URLs. --- README.md | 84 ++++++++++++++++++++++++------------------------------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 9bf2ec12f..2b260318e 100644 --- a/README.md +++ b/README.md @@ -3,59 +3,45 @@ [![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/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - [![Documentation Status](https://readthedocs.org/projects/asmresolver/badge/?version=latest)](https://asmresolver.readthedocs.io/en/latest/?badge=latest) [![Discord](https://img.shields.io/discord/961647807591243796.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/Y7DTBkbhJJ) -AsmResolver is a PE inspection library allowing .NET programmers to read, modify and write executable files. This includes .NET as well as native images. The library exposes high-level representations of the PE, while still allowing the user to access low-level structures. +AsmResolver is a Portable Executable (PE) inspection library that is able to read, modify and write executable files. This includes .NET modules as well as native images. The library exposes high-level representations of the PE, while still allowing the user to access low-level structures. AsmResolver is released under the MIT license. -## 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. +## Main Features + +AsmResolver has a lot of features. Below is a non-exhaustive list of the highlights: + +- [x] Create, read, modify, write and patch PE files. + - [x] Full access to sections, data directories and their interpretations. +- [x] Rich support for .NET modules with an intuitive API similar to `System.Reflection`. + - [x] Managed, native and dynamic method body support. + - [x] Easy metadata importing and cloning. + - [x] Managed resource file serializers and deserializers. + - [x] Support for AppHost / SingleFileHost bundles. +- [x] Read PDB symbols. + - [x] Fully managed cross-platform API (No DIA or similar required). +- [x] .NET Standard 2.0 compatible. +- [x] Documented. +- [x] Unit tested. ## Documentation -Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library. +- [Guides](https://docs.washi.dev/asmresolver) +- [API Reference](https://docs.washi.dev/asmresolver/api/core/AsmResolver.html) ## Binaries -Stable builds: +Stable Builds: -- [GitHub releases](https://github.com/Washi1337/AsmResolver/releases) -- [NuGet feed](https://www.nuget.org/packages/AsmResolver/) +- [NuGet Feed](https://www.nuget.org/packages/AsmResolver/) +- [GitHub Releases](https://github.com/Washi1337/AsmResolver/releases) -Nightly builds: +Nightly Builds: - [AppVeyor](https://ci.appveyor.com/project/Washi1337/asmresolver/build/artifacts) @@ -67,36 +53,38 @@ Nightly builds: ## 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. +The solution can be built using the .NET SDK or an IDE that works with it (e.g., Visual Studio and JetBrains Rider). The main packages target LTS versions of various .NET runtimes (.NET Standard 2.0, .NET Core 3.1 and .NET 6.0). -To build the project from the commandline, use: +To build the project from the command line, use: ```bash -$ dotnet restore $ dotnet build ``` -To run all tests, simply run: +To run all tests, use: ```bash $ dotnet test ``` +For running the tests successfully, you will need to have various versions of .NET installed (ranging from .NET Framework to .NET Core 3.1 and .NET 5+), as the unit tests verify reading binaries targeting various .NET runtimes. + ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on general workflow and code style. +- See [CONTRIBUTING.md](CONTRIBUTING.md). -## Found a bug or have questions? +## Support -Please use the [issue tracker](https://github.com/Washi1337/AsmResolver/issues). Try to be as descriptive as possible. +- [Issue Tracker](https://github.com/Washi1337/AsmResolver/issues) +- [Discussion Board](github.com/washi1337/asmresolver/discussions) +- [Discord](https://discord.gg/Y7DTBkbhJJ) -You can also join the [Discord](https://discord.gg/Y7DTBkbhJJ) to engage more directly with the community. -## Acknowledgements +## Acknowledgments -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! +AsmResolver started 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! - Special thanks to all the people who contributed [directly with code commits](https://github.com/Washi1337/AsmResolver/graphs/contributors). - 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. +If you feel you have been under-represented in these acknowledgments, feel free to reach out.