1559 lines
52 KiB
C#
1559 lines
52 KiB
C#
// dnlib: See LICENSE.txt for more info
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using dnlib.Utils;
|
|
using dnlib.DotNet.MD;
|
|
using dnlib.DotNet.Pdb;
|
|
using dnlib.DotNet.Writer;
|
|
using dnlib.PE;
|
|
using dnlib.Threading;
|
|
using dnlib.W32Resources;
|
|
using System.Diagnostics;
|
|
|
|
namespace dnlib.DotNet {
|
|
/// <summary>
|
|
/// A high-level representation of a row in the Module table
|
|
/// </summary>
|
|
public abstract class ModuleDef : IHasCustomAttribute, IHasCustomDebugInformation, IResolutionScope, IDisposable, IListListener<TypeDef>, IModule, ITypeDefFinder, IDnlibDef, ITokenResolver, ISignatureReaderHelper {
|
|
/// <summary>Default characteristics</summary>
|
|
protected const Characteristics DefaultCharacteristics = Characteristics.ExecutableImage | Characteristics.Bit32Machine;
|
|
|
|
/// <summary>Default DLL characteristics</summary>
|
|
protected const DllCharacteristics DefaultDllCharacteristics = DllCharacteristics.TerminalServerAware | DllCharacteristics.NoSeh | DllCharacteristics.NxCompat | DllCharacteristics.DynamicBase;
|
|
|
|
/// <summary>
|
|
/// The row id in its table
|
|
/// </summary>
|
|
protected uint rid;
|
|
|
|
#if THREAD_SAFE
|
|
readonly Lock theLock = Lock.Create();
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Initialize this in the ctor
|
|
/// </summary>
|
|
protected ICorLibTypes corLibTypes;
|
|
|
|
/// <summary>
|
|
/// PDB state
|
|
/// </summary>
|
|
protected PdbState pdbState;
|
|
|
|
TypeDefFinder typeDefFinder;
|
|
|
|
/// <summary>
|
|
/// Array of last used rid in each table. I.e., next free rid is value + 1
|
|
/// </summary>
|
|
protected readonly int[] lastUsedRids = new int[64];
|
|
|
|
/// <summary>Module context</summary>
|
|
protected ModuleContext context;
|
|
|
|
/// <inheritdoc/>
|
|
public MDToken MDToken => new MDToken(Table.Module, rid);
|
|
|
|
/// <inheritdoc/>
|
|
public uint Rid {
|
|
get => rid;
|
|
set => rid = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public int HasCustomAttributeTag => 7;
|
|
|
|
/// <inheritdoc/>
|
|
public int ResolutionScopeTag => 0;
|
|
|
|
/// <summary>
|
|
/// Gets/sets a user value. This is never used by dnlib. This property isn't thread safe.
|
|
/// </summary>
|
|
public object Tag {
|
|
get => tag;
|
|
set => tag = value;
|
|
}
|
|
object tag;
|
|
|
|
/// <inheritdoc/>
|
|
public ScopeType ScopeType => ScopeType.ModuleDef;
|
|
|
|
/// <inheritdoc/>
|
|
public string ScopeName => FullName;
|
|
|
|
/// <summary>
|
|
/// Gets/sets Module.Generation column
|
|
/// </summary>
|
|
public ushort Generation {
|
|
get => generation;
|
|
set => generation = value;
|
|
}
|
|
/// <summary/>
|
|
protected ushort generation;
|
|
|
|
/// <summary>
|
|
/// Gets/sets Module.Name column
|
|
/// </summary>
|
|
public UTF8String Name {
|
|
get => name;
|
|
set => name = value;
|
|
}
|
|
/// <summary>Name</summary>
|
|
protected UTF8String name;
|
|
|
|
/// <summary>
|
|
/// Gets/sets Module.Mvid column
|
|
/// </summary>
|
|
public Guid? Mvid {
|
|
get => mvid;
|
|
set => mvid = value;
|
|
}
|
|
/// <summary/>
|
|
protected Guid? mvid;
|
|
|
|
/// <summary>
|
|
/// Gets/sets Module.EncId column
|
|
/// </summary>
|
|
public Guid? EncId {
|
|
get => encId;
|
|
set => encId = value;
|
|
}
|
|
/// <summary/>
|
|
protected Guid? encId;
|
|
|
|
/// <summary>
|
|
/// Gets/sets Module.EncBaseId column
|
|
/// </summary>
|
|
public Guid? EncBaseId {
|
|
get => encBaseId;
|
|
set => encBaseId = value;
|
|
}
|
|
/// <summary/>
|
|
protected Guid? encBaseId;
|
|
|
|
/// <summary>
|
|
/// Gets all custom attributes
|
|
/// </summary>
|
|
public CustomAttributeCollection CustomAttributes {
|
|
get {
|
|
if (customAttributes is null)
|
|
InitializeCustomAttributes();
|
|
return customAttributes;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected CustomAttributeCollection customAttributes;
|
|
/// <summary>Initializes <see cref="customAttributes"/></summary>
|
|
protected virtual void InitializeCustomAttributes() =>
|
|
Interlocked.CompareExchange(ref customAttributes, new CustomAttributeCollection(), null);
|
|
|
|
/// <inheritdoc/>
|
|
public int HasCustomDebugInformationTag => 7;
|
|
|
|
/// <inheritdoc/>
|
|
public bool HasCustomDebugInfos => CustomDebugInfos.Count > 0;
|
|
|
|
/// <summary>
|
|
/// Gets all custom debug infos
|
|
/// </summary>
|
|
public IList<PdbCustomDebugInfo> CustomDebugInfos {
|
|
get {
|
|
if (customDebugInfos is null)
|
|
InitializeCustomDebugInfos();
|
|
return customDebugInfos;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected IList<PdbCustomDebugInfo> customDebugInfos;
|
|
/// <summary>Initializes <see cref="customDebugInfos"/></summary>
|
|
protected virtual void InitializeCustomDebugInfos() =>
|
|
Interlocked.CompareExchange(ref customDebugInfos, new List<PdbCustomDebugInfo>(), null);
|
|
|
|
/// <summary>
|
|
/// Gets the module's assembly. To set this value, add this <see cref="ModuleDef"/>
|
|
/// to <see cref="AssemblyDef.Modules"/>.
|
|
/// </summary>
|
|
public AssemblyDef Assembly {
|
|
get => assembly;
|
|
internal set => assembly = value;
|
|
}
|
|
/// <summary/>
|
|
protected AssemblyDef assembly;
|
|
|
|
/// <summary>
|
|
/// Gets a list of all non-nested <see cref="TypeDef"/>s. See also <see cref="GetTypes()"/>
|
|
/// </summary>
|
|
public IList<TypeDef> Types {
|
|
get {
|
|
if (types is null)
|
|
InitializeTypes();
|
|
return types;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected LazyList<TypeDef> types;
|
|
/// <summary>Initializes <see cref="types"/></summary>
|
|
protected virtual void InitializeTypes() =>
|
|
Interlocked.CompareExchange(ref types, new LazyList<TypeDef>(this), null);
|
|
|
|
/// <summary>
|
|
/// Gets a list of all <see cref="ExportedType"/>s
|
|
/// </summary>
|
|
public IList<ExportedType> ExportedTypes {
|
|
get {
|
|
if (exportedTypes is null)
|
|
InitializeExportedTypes();
|
|
return exportedTypes;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected IList<ExportedType> exportedTypes;
|
|
/// <summary>Initializes <see cref="exportedTypes"/></summary>
|
|
protected virtual void InitializeExportedTypes() =>
|
|
Interlocked.CompareExchange(ref exportedTypes, new List<ExportedType>(), null);
|
|
|
|
/// <summary>
|
|
/// Gets/sets the native entry point. Only one of <see cref="NativeEntryPoint"/> and
|
|
/// <see cref="ManagedEntryPoint"/> can be set. You write to one and the other one gets cleared.
|
|
/// </summary>
|
|
public RVA NativeEntryPoint {
|
|
get {
|
|
if (!nativeAndManagedEntryPoint_initialized)
|
|
InitializeNativeAndManagedEntryPoint();
|
|
return nativeEntryPoint;
|
|
}
|
|
set {
|
|
#if THREAD_SAFE
|
|
theLock.EnterWriteLock(); try {
|
|
#endif
|
|
nativeEntryPoint = value;
|
|
managedEntryPoint = null;
|
|
Cor20HeaderFlags |= ComImageFlags.NativeEntryPoint;
|
|
nativeAndManagedEntryPoint_initialized = true;
|
|
#if THREAD_SAFE
|
|
} finally { theLock.ExitWriteLock(); }
|
|
#endif
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Gets/sets the managed entry point. Only one of <see cref="NativeEntryPoint"/> and
|
|
/// <see cref="ManagedEntryPoint"/> can be set. You write to one and the other one gets cleared.
|
|
/// </summary>
|
|
public IManagedEntryPoint ManagedEntryPoint {
|
|
get {
|
|
if (!nativeAndManagedEntryPoint_initialized)
|
|
InitializeNativeAndManagedEntryPoint();
|
|
return managedEntryPoint;
|
|
}
|
|
set {
|
|
#if THREAD_SAFE
|
|
theLock.EnterWriteLock(); try {
|
|
#endif
|
|
nativeEntryPoint = 0;
|
|
managedEntryPoint = value;
|
|
Cor20HeaderFlags &= ~ComImageFlags.NativeEntryPoint;
|
|
nativeAndManagedEntryPoint_initialized = true;
|
|
#if THREAD_SAFE
|
|
} finally { theLock.ExitWriteLock(); }
|
|
#endif
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected RVA nativeEntryPoint;
|
|
/// <summary/>
|
|
protected IManagedEntryPoint managedEntryPoint;
|
|
/// <summary/>
|
|
protected bool nativeAndManagedEntryPoint_initialized;
|
|
|
|
void InitializeNativeAndManagedEntryPoint() {
|
|
#if THREAD_SAFE
|
|
theLock.EnterWriteLock(); try {
|
|
#endif
|
|
if (nativeAndManagedEntryPoint_initialized)
|
|
return;
|
|
nativeEntryPoint = GetNativeEntryPoint_NoLock();
|
|
managedEntryPoint = GetManagedEntryPoint_NoLock();
|
|
nativeAndManagedEntryPoint_initialized = true;
|
|
#if THREAD_SAFE
|
|
} finally { theLock.ExitWriteLock(); }
|
|
#endif
|
|
}
|
|
/// <summary>Called to initialize <see cref="nativeEntryPoint"/></summary>
|
|
protected virtual RVA GetNativeEntryPoint_NoLock() => 0;
|
|
/// <summary>Called to initialize <see cref="managedEntryPoint"/></summary>
|
|
protected virtual IManagedEntryPoint GetManagedEntryPoint_NoLock() => null;
|
|
|
|
/// <inheritdoc/>
|
|
public bool HasCustomAttributes => CustomAttributes.Count > 0;
|
|
|
|
/// <summary>
|
|
/// Gets/sets the entry point method
|
|
/// </summary>
|
|
public MethodDef EntryPoint {
|
|
get => ManagedEntryPoint as MethodDef;
|
|
set => ManagedEntryPoint = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="NativeEntryPoint"/> is non-zero
|
|
/// </summary>
|
|
public bool IsNativeEntryPointValid => NativeEntryPoint != 0;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="ManagedEntryPoint"/> is non-null
|
|
/// </summary>
|
|
public bool IsManagedEntryPointValid => ManagedEntryPoint is not null;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="EntryPoint"/> is non-null
|
|
/// </summary>
|
|
public bool IsEntryPointValid => EntryPoint is not null;
|
|
|
|
/// <summary>
|
|
/// Gets a list of all <see cref="Resource"/>s
|
|
/// </summary>
|
|
public ResourceCollection Resources {
|
|
get {
|
|
if (resources is null)
|
|
InitializeResources();
|
|
return resources;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected ResourceCollection resources;
|
|
/// <summary>Initializes <see cref="resources"/></summary>
|
|
protected virtual void InitializeResources() =>
|
|
Interlocked.CompareExchange(ref resources, new ResourceCollection(), null);
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="VTableFixups"/>. This is <c>null</c> if there are no
|
|
/// vtable fixups.
|
|
/// </summary>
|
|
public VTableFixups VTableFixups {
|
|
get {
|
|
if (!vtableFixups_isInitialized)
|
|
InitializeVTableFixups();
|
|
return vtableFixups;
|
|
}
|
|
set {
|
|
#if THREAD_SAFE
|
|
theLock.EnterWriteLock(); try {
|
|
#endif
|
|
vtableFixups = value;
|
|
vtableFixups_isInitialized = true;
|
|
#if THREAD_SAFE
|
|
} finally { theLock.ExitWriteLock(); }
|
|
#endif
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected VTableFixups vtableFixups;
|
|
/// <summary/>
|
|
protected bool vtableFixups_isInitialized;
|
|
|
|
void InitializeVTableFixups() {
|
|
#if THREAD_SAFE
|
|
theLock.EnterWriteLock(); try {
|
|
#endif
|
|
if (vtableFixups_isInitialized)
|
|
return;
|
|
vtableFixups = GetVTableFixups_NoLock();
|
|
vtableFixups_isInitialized = true;
|
|
#if THREAD_SAFE
|
|
} finally { theLock.ExitWriteLock(); }
|
|
#endif
|
|
}
|
|
|
|
/// <summary>Called to initialize <see cref="vtableFixups"/></summary>
|
|
protected virtual VTableFixups GetVTableFixups_NoLock() => null;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if there's at least one <see cref="TypeDef"/> in <see cref="Types"/>
|
|
/// </summary>
|
|
public bool HasTypes => Types.Count > 0;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if there's at least one <see cref="ExportedType"/> in <see cref="ExportedTypes"/>
|
|
/// </summary>
|
|
public bool HasExportedTypes => ExportedTypes.Count > 0;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if there's at least one <see cref="Resource"/> in <see cref="Resources"/>
|
|
/// </summary>
|
|
public bool HasResources => Resources.Count > 0;
|
|
|
|
/// <inheritdoc/>
|
|
public string FullName => UTF8String.ToSystemStringOrEmpty(name);
|
|
|
|
/// <summary>
|
|
/// Gets/sets the path of the module or an empty string if it wasn't loaded from disk
|
|
/// </summary>
|
|
public string Location {
|
|
get => location;
|
|
set => location = value;
|
|
}
|
|
/// <summary/>
|
|
protected string location;
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="ICorLibTypes"/>
|
|
/// </summary>
|
|
public ICorLibTypes CorLibTypes => corLibTypes;
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="TypeDefFinder"/> instance
|
|
/// </summary>
|
|
TypeDefFinder TypeDefFinder {
|
|
get {
|
|
if (typeDefFinder is null)
|
|
Interlocked.CompareExchange(ref typeDefFinder, new TypeDefFinder(Types), null);
|
|
return typeDefFinder;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the module context. This is never <c>null</c>.
|
|
/// </summary>
|
|
public ModuleContext Context {
|
|
get {
|
|
if (context is null)
|
|
Interlocked.CompareExchange(ref context, new ModuleContext(), null);
|
|
return context;
|
|
}
|
|
set => context = value ?? new ModuleContext();
|
|
}
|
|
|
|
/// <summary>
|
|
/// If <c>true</c>, the <see cref="TypeDef"/> cache is enabled. The cache is used by
|
|
/// <see cref="Find(string,bool)"/> and <see cref="Find(TypeRef)"/> to find types.
|
|
/// <br/><br/>
|
|
/// <c>IMPORTANT:</c> Only enable the cache if this module's types keep their exact
|
|
/// name, namespace, and declaring type and if <c>no</c> type is either added or
|
|
/// removed from <see cref="Types"/> or from any type that is reachable from the
|
|
/// top-level types in <see cref="Types"/> (i.e., any type owned by this module).
|
|
/// This is disabled by default. When disabled, all calls to <see cref="Find(string,bool)"/>
|
|
/// and <see cref="Find(TypeRef)"/> will result in a slow <c>O(n)</c> (linear) search.
|
|
/// </summary>
|
|
/// <seealso cref="ResetTypeDefFindCache()"/>
|
|
public bool EnableTypeDefFindCache {
|
|
get => TypeDefFinder.IsCacheEnabled;
|
|
set => TypeDefFinder.IsCacheEnabled = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if this is the manifest (main) module
|
|
/// </summary>
|
|
public bool IsManifestModule {
|
|
get {
|
|
var asm = assembly;
|
|
return asm is not null && asm.ManifestModule == this;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the global (aka. <Module>) type or <c>null</c> if there are no types
|
|
/// </summary>
|
|
public TypeDef GlobalType => Types.Count == 0 ? null : Types[0];
|
|
|
|
/// <summary>
|
|
/// true if it's the core library module, false if it's not the core library module,
|
|
/// and null if it's not known.
|
|
/// </summary>
|
|
public bool? IsCoreLibraryModule { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets/sets the Win32 resources
|
|
/// </summary>
|
|
public Win32Resources Win32Resources {
|
|
get {
|
|
if (!win32Resources_isInitialized)
|
|
InitializeWin32Resources();
|
|
return win32Resources;
|
|
}
|
|
set {
|
|
#if THREAD_SAFE
|
|
theLock.EnterWriteLock(); try {
|
|
#endif
|
|
win32Resources = value;
|
|
win32Resources_isInitialized = true;
|
|
#if THREAD_SAFE
|
|
} finally { theLock.ExitWriteLock(); }
|
|
#endif
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected Win32Resources win32Resources;
|
|
/// <summary/>
|
|
protected bool win32Resources_isInitialized;
|
|
|
|
void InitializeWin32Resources() {
|
|
#if THREAD_SAFE
|
|
theLock.EnterWriteLock(); try {
|
|
#endif
|
|
if (win32Resources_isInitialized)
|
|
return;
|
|
win32Resources = GetWin32Resources_NoLock();
|
|
win32Resources_isInitialized = true;
|
|
#if THREAD_SAFE
|
|
} finally { theLock.ExitWriteLock(); }
|
|
#endif
|
|
}
|
|
|
|
/// <summary>Called to initialize <see cref="win32Resources"/></summary>
|
|
protected virtual Win32Resources GetWin32Resources_NoLock() => null;
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="dnlib.DotNet.Pdb.PdbState"/>. This is <c>null</c> if no PDB file
|
|
/// has been loaded or if no PDB file could be found.
|
|
/// </summary>
|
|
public PdbState PdbState => pdbState;
|
|
|
|
/// <summary>
|
|
/// Module kind
|
|
/// </summary>
|
|
public ModuleKind Kind { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets/sets the characteristics (from PE file header)
|
|
/// </summary>
|
|
public Characteristics Characteristics { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets/sets the DLL characteristics (from PE optional header)
|
|
/// </summary>
|
|
public DllCharacteristics DllCharacteristics { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets/sets the runtime version which is stored in the metadata header.
|
|
/// See <see cref="MDHeaderRuntimeVersion"/>.
|
|
/// </summary>
|
|
/// <remarks>Not thread safe</remarks>
|
|
public string RuntimeVersion {
|
|
get => runtimeVersion;
|
|
set {
|
|
if (runtimeVersion != value) {
|
|
runtimeVersion = value;
|
|
cachedWinMDStatus = null;
|
|
runtimeVersionWinMD = null;
|
|
winMDVersion = null;
|
|
}
|
|
}
|
|
}
|
|
string runtimeVersion;
|
|
|
|
/// <summary>
|
|
/// Gets the WinMD status
|
|
/// </summary>
|
|
/// <remarks>Not thread safe</remarks>
|
|
public WinMDStatus WinMDStatus {
|
|
get {
|
|
var cval = cachedWinMDStatus;
|
|
if (cval is not null)
|
|
return cval.Value;
|
|
cachedWinMDStatus = cval = CalculateWinMDStatus(RuntimeVersion);
|
|
return cval.Value;
|
|
}
|
|
}
|
|
WinMDStatus? cachedWinMDStatus;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if this is a WinMD file
|
|
/// </summary>
|
|
public bool IsWinMD => WinMDStatus != WinMDStatus.None;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if this is a managed WinMD file
|
|
/// </summary>
|
|
public bool IsManagedWinMD => WinMDStatus == WinMDStatus.Managed;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if this is a pure (non-managed) WinMD file
|
|
/// </summary>
|
|
public bool IsPureWinMD => WinMDStatus == WinMDStatus.Pure;
|
|
|
|
/// <summary>
|
|
/// Gets the CLR runtime version of the managed WinMD file or <c>null</c> if none. This is
|
|
/// similar to <see cref="RuntimeVersion"/> for normal non-WinMD files.
|
|
/// </summary>
|
|
/// <remarks>Not thread safe</remarks>
|
|
public string RuntimeVersionWinMD {
|
|
get {
|
|
var rtver = runtimeVersionWinMD;
|
|
if (rtver is not null)
|
|
return rtver;
|
|
runtimeVersionWinMD = rtver = CalculateRuntimeVersionWinMD(RuntimeVersion);
|
|
return rtver;
|
|
}
|
|
}
|
|
string runtimeVersionWinMD;
|
|
|
|
/// <summary>
|
|
/// Gets the WinMD version or <c>null</c> if none
|
|
/// </summary>
|
|
/// <remarks>Not thread safe</remarks>
|
|
public string WinMDVersion {
|
|
get {
|
|
var ver = winMDVersion;
|
|
if (ver is not null)
|
|
return ver;
|
|
winMDVersion = ver = CalculateWinMDVersion(RuntimeVersion);
|
|
return ver;
|
|
}
|
|
}
|
|
string winMDVersion;
|
|
|
|
static WinMDStatus CalculateWinMDStatus(string version) {
|
|
if (version is null)
|
|
return WinMDStatus.None;
|
|
if (!version.StartsWith("WindowsRuntime ", StringComparison.Ordinal))
|
|
return WinMDStatus.None;
|
|
|
|
return version.IndexOf(';') < 0 ? WinMDStatus.Pure : WinMDStatus.Managed;
|
|
}
|
|
|
|
static string CalculateRuntimeVersionWinMD(string version) {
|
|
// Original parser code:
|
|
// CoreCLR file: src/md/winmd/adapter.cpp
|
|
// Func: WinMDAdapter::Create(IMDCommon *pRawMDCommon, /*[out]*/ WinMDAdapter **ppAdapter)
|
|
if (version is null)
|
|
return null;
|
|
if (!version.StartsWith("WindowsRuntime ", StringComparison.Ordinal))
|
|
return null;
|
|
int index = version.IndexOf(';');
|
|
if (index < 0)
|
|
return null;
|
|
var s = version.Substring(index + 1);
|
|
if (s.StartsWith("CLR", StringComparison.OrdinalIgnoreCase))
|
|
s = s.Substring(3);
|
|
s = s.TrimStart(' ');
|
|
|
|
return s;
|
|
}
|
|
|
|
static string CalculateWinMDVersion(string version) {
|
|
if (version is null)
|
|
return null;
|
|
if (!version.StartsWith("WindowsRuntime ", StringComparison.Ordinal))
|
|
return null;
|
|
int index = version.IndexOf(';');
|
|
if (index < 0)
|
|
return version;
|
|
return version.Substring(0, index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v1.0 string (only the major
|
|
/// and minor version numbers are checked)
|
|
/// </summary>
|
|
public bool IsClr10 {
|
|
get {
|
|
var ver = RuntimeVersion ?? string.Empty;
|
|
return ver.StartsWith(MDHeaderRuntimeVersion.MS_CLR_10_PREFIX) ||
|
|
ver.StartsWith(MDHeaderRuntimeVersion.MS_CLR_10_PREFIX_X86RETAIL) ||
|
|
ver == MDHeaderRuntimeVersion.MS_CLR_10_RETAIL ||
|
|
ver == MDHeaderRuntimeVersion.MS_CLR_10_COMPLUS;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v1.0 string
|
|
/// </summary>
|
|
public bool IsClr10Exactly =>
|
|
RuntimeVersion == MDHeaderRuntimeVersion.MS_CLR_10 ||
|
|
RuntimeVersion == MDHeaderRuntimeVersion.MS_CLR_10_X86RETAIL ||
|
|
RuntimeVersion == MDHeaderRuntimeVersion.MS_CLR_10_RETAIL ||
|
|
RuntimeVersion == MDHeaderRuntimeVersion.MS_CLR_10_COMPLUS;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v1.1 string (only the major
|
|
/// and minor version numbers are checked)
|
|
/// </summary>
|
|
public bool IsClr11 => (RuntimeVersion ?? string.Empty).StartsWith(MDHeaderRuntimeVersion.MS_CLR_11_PREFIX);
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v1.1 string
|
|
/// </summary>
|
|
public bool IsClr11Exactly => RuntimeVersion == MDHeaderRuntimeVersion.MS_CLR_11;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v1.0 or v1.1 string (only the
|
|
/// major and minor version numbers are checked)
|
|
/// </summary>
|
|
public bool IsClr1x => IsClr10 || IsClr11;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v1.0 or v1.1 string
|
|
/// </summary>
|
|
public bool IsClr1xExactly => IsClr10Exactly || IsClr11Exactly;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v2.0 string (only the major
|
|
/// and minor version numbers are checked)
|
|
/// </summary>
|
|
public bool IsClr20 => (RuntimeVersion ?? string.Empty).StartsWith(MDHeaderRuntimeVersion.MS_CLR_20_PREFIX);
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v2.0 string
|
|
/// </summary>
|
|
public bool IsClr20Exactly => RuntimeVersion == MDHeaderRuntimeVersion.MS_CLR_20;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v4.0 string (only the major
|
|
/// and minor version numbers are checked)
|
|
/// </summary>
|
|
public bool IsClr40 => (RuntimeVersion ?? string.Empty).StartsWith(MDHeaderRuntimeVersion.MS_CLR_40_PREFIX);
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the CLR v4.0 string
|
|
/// </summary>
|
|
public bool IsClr40Exactly => RuntimeVersion == MDHeaderRuntimeVersion.MS_CLR_40;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the ECMA 2002 string
|
|
/// </summary>
|
|
public bool IsEcma2002 => RuntimeVersion == MDHeaderRuntimeVersion.ECMA_2002;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="RuntimeVersion"/> is the ECMA 2005 string
|
|
/// </summary>
|
|
public bool IsEcma2005 => RuntimeVersion == MDHeaderRuntimeVersion.ECMA_2005;
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="Machine"/> (from PE header)
|
|
/// </summary>
|
|
public Machine Machine { get; set; }
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="Machine"/> is <see cref="PE.Machine.I386"/>, <see cref="PE.Machine.I386_Native_Apple"/>, ...
|
|
/// </summary>
|
|
public bool IsI386 => Machine.IsI386();
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="Machine"/> is <see cref="PE.Machine.IA64"/>
|
|
/// </summary>
|
|
public bool IsIA64 => Machine == Machine.IA64;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="Machine"/> is <see cref="PE.Machine.AMD64"/>, <see cref="PE.Machine.AMD64_Native_Apple"/>, ...
|
|
/// </summary>
|
|
public bool IsAMD64 => Machine.IsAMD64();
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="Machine"/> is <see cref="PE.Machine.ARMNT"/>, <see cref="PE.Machine.ARMNT_Native_Apple"/>, ...
|
|
/// </summary>
|
|
public bool IsARM => Machine.IsARMNT();
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="Machine"/> is <see cref="PE.Machine.ARM64"/>, <see cref="PE.Machine.ARM64_Native_Apple"/>, ...
|
|
/// </summary>
|
|
public bool IsARM64 => Machine.IsARM64();
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="Machine"/> is s390x, <see cref="PE.Machine.S390X_Native_Apple"/>, ...
|
|
/// </summary>
|
|
public bool IsS390x => Machine.IsS390x();
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="Cor20HeaderFlags"/> (from .NET header)
|
|
/// </summary>
|
|
public ComImageFlags Cor20HeaderFlags {
|
|
get => (ComImageFlags)cor20HeaderFlags;
|
|
set => cor20HeaderFlags = (int)value;
|
|
}
|
|
/// <summary/>
|
|
protected int cor20HeaderFlags;
|
|
|
|
/// <summary>
|
|
/// Gets/sets the runtime version number in the COR20 header. The major version is
|
|
/// in the high 16 bits. The minor version is in the low 16 bits. This is normally 2.5
|
|
/// (0x00020005), but if it's .NET Framework 1.x, it should be 2.0 (0x00020000). If this is
|
|
/// <c>null</c>, the default value will be used when saving the module (2.0 if CLR 1.x,
|
|
/// and 2.5 if not CLR 1.x).
|
|
/// </summary>
|
|
public uint? Cor20HeaderRuntimeVersion { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets the tables header version. The major version is in the upper 8 bits and the
|
|
/// minor version is in the lower 8 bits. .NET Framework 1.0/1.1 use version 1.0 (0x0100) and
|
|
/// .NET Framework 2.x and later use version 2.0 (0x0200). 1.0 has no support for generics,
|
|
/// 1.1 has support for generics (GenericParam rows have an extra Kind column),
|
|
/// and 2.0 has support for generics (GenericParam rows have the standard 4 columns).
|
|
/// No other version is supported. If this is <c>null</c>, the default version is
|
|
/// used (1.0 if .NET Framework 1.x, else 2.0).
|
|
/// </summary>
|
|
public ushort? TablesHeaderVersion { get; set; }
|
|
|
|
/// <summary>
|
|
/// Set or clear flags in <see cref="cor20HeaderFlags"/>
|
|
/// </summary>
|
|
/// <param name="set"><c>true</c> if flags should be set, <c>false</c> if flags should
|
|
/// be cleared</param>
|
|
/// <param name="flags">Flags to set or clear</param>
|
|
void ModifyComImageFlags(bool set, ComImageFlags flags) {
|
|
#if THREAD_SAFE
|
|
int origVal, newVal;
|
|
do {
|
|
origVal = cor20HeaderFlags;
|
|
if (set)
|
|
newVal = origVal | (int)flags;
|
|
else
|
|
newVal = origVal & ~(int)flags;
|
|
} while (Interlocked.CompareExchange(ref cor20HeaderFlags, newVal, origVal) != origVal);
|
|
#else
|
|
if (set)
|
|
cor20HeaderFlags |= (int)flags;
|
|
else
|
|
cor20HeaderFlags &= ~(int)flags;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="ComImageFlags.ILOnly"/> bit
|
|
/// </summary>
|
|
public bool IsILOnly {
|
|
get => ((ComImageFlags)cor20HeaderFlags & ComImageFlags.ILOnly) != 0;
|
|
set => ModifyComImageFlags(value, ComImageFlags.ILOnly);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="ComImageFlags.Bit32Required"/> bit
|
|
/// </summary>
|
|
public bool Is32BitRequired {
|
|
get => ((ComImageFlags)cor20HeaderFlags & ComImageFlags.Bit32Required) != 0;
|
|
set => ModifyComImageFlags(value, ComImageFlags.Bit32Required);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="ComImageFlags.StrongNameSigned"/> bit
|
|
/// </summary>
|
|
public bool IsStrongNameSigned {
|
|
get => ((ComImageFlags)cor20HeaderFlags & ComImageFlags.StrongNameSigned) != 0;
|
|
set => ModifyComImageFlags(value, ComImageFlags.StrongNameSigned);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="ComImageFlags.NativeEntryPoint"/> bit
|
|
/// </summary>
|
|
public bool HasNativeEntryPoint {
|
|
get => ((ComImageFlags)cor20HeaderFlags & ComImageFlags.NativeEntryPoint) != 0;
|
|
set => ModifyComImageFlags(value, ComImageFlags.NativeEntryPoint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="ComImageFlags.Bit32Preferred"/> bit
|
|
/// </summary>
|
|
public bool Is32BitPreferred {
|
|
get => ((ComImageFlags)cor20HeaderFlags & ComImageFlags.Bit32Preferred) != 0;
|
|
set => ModifyComImageFlags(value, ComImageFlags.Bit32Preferred);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void Dispose() {
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose method
|
|
/// </summary>
|
|
/// <param name="disposing"><c>true</c> if called by <see cref="Dispose()"/></param>
|
|
protected virtual void Dispose(bool disposing) {
|
|
if (!disposing)
|
|
return;
|
|
var tdf = typeDefFinder;
|
|
if (tdf is not null) {
|
|
tdf.Dispose();
|
|
typeDefFinder = null;
|
|
}
|
|
pdbState?.Dispose();
|
|
pdbState = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all the types (including nested types) present in this module
|
|
/// </summary>
|
|
public IEnumerable<TypeDef> GetTypes() => AllTypesHelper.Types(Types);
|
|
|
|
/// <summary>
|
|
/// Adds <paramref name="typeDef"/> as a non-nested type. If it's already nested, its
|
|
/// <see cref="TypeDef.DeclaringType"/> will be set to <c>null</c>.
|
|
/// </summary>
|
|
/// <param name="typeDef">The <see cref="TypeDef"/> to insert</param>
|
|
public void AddAsNonNestedType(TypeDef typeDef) {
|
|
if (typeDef is null)
|
|
return;
|
|
typeDef.DeclaringType = null;
|
|
Types.Add(typeDef);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the <c>rid</c> to the next free <c>rid</c> available. It's only updated if
|
|
/// the original <c>rid</c> is 0.
|
|
/// </summary>
|
|
/// <typeparam name="T">IMDTokenProvider</typeparam>
|
|
/// <param name="tableRow">The row that should be updated</param>
|
|
/// <returns>Returns the input</returns>
|
|
public T UpdateRowId<T>(T tableRow) where T : IMDTokenProvider {
|
|
if (tableRow != null && tableRow.Rid == 0)
|
|
tableRow.Rid = GetNextFreeRid(tableRow.MDToken.Table);
|
|
return tableRow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the <c>rid</c> to the next free <c>rid</c> available.
|
|
/// </summary>
|
|
/// <typeparam name="T">IMDTokenProvider</typeparam>
|
|
/// <param name="tableRow">The row that should be updated</param>
|
|
/// <returns>Returns the input</returns>
|
|
public T ForceUpdateRowId<T>(T tableRow) where T : IMDTokenProvider {
|
|
if (tableRow != null)
|
|
tableRow.Rid = GetNextFreeRid(tableRow.MDToken.Table);
|
|
return tableRow;
|
|
}
|
|
|
|
uint GetNextFreeRid(Table table) {
|
|
var lastUsedRids = this.lastUsedRids;
|
|
if ((uint)table >= lastUsedRids.Length)
|
|
return 0;
|
|
return (uint)Interlocked.Increment(ref lastUsedRids[(int)table]) & 0x00FFFFFF;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="Type"/> as a <see cref="ITypeDefOrRef"/>
|
|
/// </summary>
|
|
/// <param name="type">The type</param>
|
|
/// <returns>The imported type or <c>null</c> if <paramref name="type"/> is invalid</returns>
|
|
public ITypeDefOrRef Import(Type type) => new Importer(this).Import(type);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="Type"/> as a <see cref="TypeSig"/>
|
|
/// </summary>
|
|
/// <param name="type">The type</param>
|
|
/// <returns>The imported type or <c>null</c> if <paramref name="type"/> is invalid</returns>
|
|
public TypeSig ImportAsTypeSig(Type type) => new Importer(this).ImportAsTypeSig(type);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="FieldInfo"/> as a <see cref="MemberRef"/>
|
|
/// </summary>
|
|
/// <param name="fieldInfo">The field</param>
|
|
/// <returns>The imported field or <c>null</c> if <paramref name="fieldInfo"/> is invalid
|
|
/// or if we failed to import the field</returns>
|
|
public MemberRef Import(FieldInfo fieldInfo) => (MemberRef)new Importer(this).Import(fieldInfo);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="MethodBase"/> as a <see cref="IMethod"/>. This will be either
|
|
/// a <see cref="MemberRef"/> or a <see cref="MethodSpec"/>.
|
|
/// </summary>
|
|
/// <param name="methodBase">The method</param>
|
|
/// <returns>The imported method or <c>null</c> if <paramref name="methodBase"/> is invalid
|
|
/// or if we failed to import the method</returns>
|
|
public IMethod Import(MethodBase methodBase) => new Importer(this).Import(methodBase);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="IType"/>
|
|
/// </summary>
|
|
/// <param name="type">The type</param>
|
|
/// <returns>The imported type or <c>null</c></returns>
|
|
public IType Import(IType type) => new Importer(this).Import(type);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="TypeDef"/> as a <see cref="TypeRef"/>
|
|
/// </summary>
|
|
/// <param name="type">The type</param>
|
|
/// <returns>The imported type or <c>null</c></returns>
|
|
public TypeRef Import(TypeDef type) => (TypeRef)new Importer(this).Import(type);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="TypeRef"/>
|
|
/// </summary>
|
|
/// <param name="type">The type</param>
|
|
/// <returns>The imported type or <c>null</c></returns>
|
|
public TypeRef Import(TypeRef type) => (TypeRef)new Importer(this).Import(type);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="TypeSpec"/>
|
|
/// </summary>
|
|
/// <param name="type">The type</param>
|
|
/// <returns>The imported type or <c>null</c></returns>
|
|
public TypeSpec Import(TypeSpec type) => new Importer(this).Import(type);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="TypeSig"/>
|
|
/// </summary>
|
|
/// <param name="type">The type</param>
|
|
/// <returns>The imported type or <c>null</c></returns>
|
|
public TypeSig Import(TypeSig type) => new Importer(this).Import(type);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="IField"/>
|
|
/// </summary>
|
|
/// <param name="field">The field</param>
|
|
/// <returns>The imported type or <c>null</c> if <paramref name="field"/> is invalid</returns>
|
|
public MemberRef Import(IField field) => (MemberRef)new Importer(this).Import(field);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="FieldDef"/> as a <see cref="MemberRef"/>
|
|
/// </summary>
|
|
/// <param name="field">The field</param>
|
|
/// <returns>The imported type or <c>null</c> if <paramref name="field"/> is invalid</returns>
|
|
public MemberRef Import(FieldDef field) => (MemberRef)new Importer(this).Import(field);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="IMethod"/>
|
|
/// </summary>
|
|
/// <param name="method">The method</param>
|
|
/// <returns>The imported method or <c>null</c> if <paramref name="method"/> is invalid</returns>
|
|
public IMethod Import(IMethod method) => new Importer(this).Import(method);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="MethodDef"/> as a <see cref="MemberRef"/>
|
|
/// </summary>
|
|
/// <param name="method">The method</param>
|
|
/// <returns>The imported method or <c>null</c> if <paramref name="method"/> is invalid</returns>
|
|
public MemberRef Import(MethodDef method) => (MemberRef)new Importer(this).Import(method);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="MethodSpec"/>
|
|
/// </summary>
|
|
/// <param name="method">The method</param>
|
|
/// <returns>The imported method or <c>null</c> if <paramref name="method"/> is invalid</returns>
|
|
public MethodSpec Import(MethodSpec method) => new Importer(this).Import(method);
|
|
|
|
/// <summary>
|
|
/// Imports a <see cref="MemberRef"/>
|
|
/// </summary>
|
|
/// <param name="memberRef">The member ref</param>
|
|
/// <returns>The imported member ref or <c>null</c> if <paramref name="memberRef"/> is invalid</returns>
|
|
public MemberRef Import(MemberRef memberRef) => new Importer(this).Import(memberRef);
|
|
|
|
/// <summary>
|
|
/// Writes the module to a file on disk. If the file exists, it will be overwritten.
|
|
/// </summary>
|
|
/// <param name="filename">Filename</param>
|
|
public void Write(string filename) => Write(filename, null);
|
|
|
|
/// <summary>
|
|
/// Writes the module to a file on disk. If the file exists, it will be overwritten.
|
|
/// </summary>
|
|
/// <param name="filename">Filename</param>
|
|
/// <param name="options">Writer options</param>
|
|
public void Write(string filename, ModuleWriterOptions options) {
|
|
var writer = new ModuleWriter(this, options ?? new ModuleWriterOptions(this));
|
|
writer.Write(filename);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the module to a stream.
|
|
/// </summary>
|
|
/// <param name="dest">Destination stream</param>
|
|
public void Write(Stream dest) => Write(dest, null);
|
|
|
|
/// <summary>
|
|
/// Writes the module to a stream.
|
|
/// </summary>
|
|
/// <param name="dest">Destination stream</param>
|
|
/// <param name="options">Writer options</param>
|
|
public void Write(Stream dest, ModuleWriterOptions options) {
|
|
var writer = new ModuleWriter(this, options ?? new ModuleWriterOptions(this));
|
|
writer.Write(dest);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the <see cref="TypeDef"/> cache which can be enabled by setting
|
|
/// <see cref="EnableTypeDefFindCache"/> to <c>true</c>. Use this method if the cache is
|
|
/// enabled but some of the types have been modified (eg. removed, added, renamed).
|
|
/// </summary>
|
|
public void ResetTypeDefFindCache() => TypeDefFinder.ResetCache();
|
|
|
|
/// <summary>
|
|
/// Finds a <see cref="ResourceData"/>
|
|
/// </summary>
|
|
/// <param name="type">Type</param>
|
|
/// <param name="name">Name</param>
|
|
/// <param name="langId">Language ID</param>
|
|
/// <returns>The <see cref="ResourceData"/> or <c>null</c> if none found</returns>
|
|
public ResourceData FindWin32ResourceData(ResourceName type, ResourceName name, ResourceName langId) => Win32Resources?.Find(type, name, langId);
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="dnlib.DotNet.Pdb.PdbState"/>
|
|
/// </summary>
|
|
/// <param name="pdbFileKind">PDB file kind</param>
|
|
public void CreatePdbState(PdbFileKind pdbFileKind) => SetPdbState(new PdbState(this, pdbFileKind));
|
|
|
|
/// <summary>
|
|
/// Sets a <see cref="dnlib.DotNet.Pdb.PdbState"/>
|
|
/// </summary>
|
|
/// <param name="pdbState">New <see cref="dnlib.DotNet.Pdb.PdbState"/></param>
|
|
public void SetPdbState(PdbState pdbState) {
|
|
if (pdbState is null)
|
|
throw new ArgumentNullException(nameof(pdbState));
|
|
var orig = Interlocked.CompareExchange(ref this.pdbState, pdbState, null);
|
|
if (orig is not null)
|
|
throw new InvalidOperationException("PDB file has already been initialized");
|
|
}
|
|
|
|
uint GetCor20RuntimeVersion() {
|
|
var rtVer = Cor20HeaderRuntimeVersion;
|
|
if (rtVer is not null)
|
|
return rtVer.Value;
|
|
return IsClr1x ? 0x00020000U : 0x00020005;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the size of a pointer. Assumes it's 32-bit if pointer size is unknown or
|
|
/// if it can be 32-bit or 64-bit.
|
|
/// </summary>
|
|
/// <returns>Size of a pointer (4 or 8)</returns>
|
|
public int GetPointerSize() => GetPointerSize(4);
|
|
|
|
/// <summary>
|
|
/// Returns the size of a pointer
|
|
/// </summary>
|
|
/// <param name="defaultPointerSize">Default pointer size if it's not known or if it
|
|
/// can be 32-bit or 64-bit</param>
|
|
/// <returns>Size of a pointer (4 or 8)</returns>
|
|
public int GetPointerSize(int defaultPointerSize) => GetPointerSize(defaultPointerSize, defaultPointerSize);
|
|
|
|
/// <summary>
|
|
/// Returns the size of a pointer
|
|
/// </summary>
|
|
/// <param name="defaultPointerSize">Default pointer size</param>
|
|
/// <param name="prefer32bitPointerSize">Pointer size if it's prefer-32-bit (should usually be 4)</param>
|
|
/// <returns></returns>
|
|
public int GetPointerSize(int defaultPointerSize, int prefer32bitPointerSize) {
|
|
var machine = Machine;
|
|
if (machine.Is64Bit())
|
|
return 8;
|
|
if (!machine.IsI386())
|
|
return 4;
|
|
|
|
// Machine is I386 so it's either x86 or platform neutral
|
|
|
|
// If the runtime version is < 2.5, then it's always loaded as a 32-bit process.
|
|
if (GetCor20RuntimeVersion() < 0x00020005)
|
|
return 4;
|
|
|
|
// If it's a 32-bit PE header, and ILOnly is cleared, it's always loaded as a
|
|
// 32-bit process.
|
|
var flags = (ComImageFlags)cor20HeaderFlags;
|
|
if ((flags & ComImageFlags.ILOnly) == 0)
|
|
return 4;
|
|
|
|
// 32-bit Preferred flag is new in .NET Framework 4.5. See CorHdr.h in Windows SDK for more info
|
|
switch (flags & (ComImageFlags.Bit32Required | ComImageFlags.Bit32Preferred)) {
|
|
case 0:
|
|
// Machine and ILOnly flag should be checked
|
|
break;
|
|
|
|
case ComImageFlags.Bit32Preferred:
|
|
// Illegal
|
|
break;
|
|
|
|
case ComImageFlags.Bit32Required:
|
|
// x86 image (32-bit process)
|
|
return 4;
|
|
|
|
case ComImageFlags.Bit32Required | ComImageFlags.Bit32Preferred:
|
|
// Platform neutral but prefers to be 32-bit
|
|
return prefer32bitPointerSize;
|
|
}
|
|
|
|
return defaultPointerSize;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<TypeDef>.OnLazyAdd(int index, ref TypeDef value) {
|
|
#if DEBUG
|
|
if (value.DeclaringType is not null)
|
|
throw new InvalidOperationException("Added type's DeclaringType is not null");
|
|
#endif
|
|
value.Module2 = this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<TypeDef>.OnAdd(int index, TypeDef value) {
|
|
if (value.DeclaringType is not null)
|
|
throw new InvalidOperationException("Nested type is already owned by another type. Set DeclaringType to null first.");
|
|
if (value.Module is not null)
|
|
throw new InvalidOperationException("Type is already owned by another module. Remove it from that module's type list.");
|
|
value.Module2 = this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<TypeDef>.OnRemove(int index, TypeDef value) => value.Module2 = null;
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<TypeDef>.OnResize(int index) {
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<TypeDef>.OnClear() {
|
|
foreach (var type in types.GetEnumerable_NoLock())
|
|
type.Module2 = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds a <see cref="TypeDef"/>. For speed, enable <see cref="EnableTypeDefFindCache"/>
|
|
/// if possible (read the documentation first).
|
|
/// </summary>
|
|
/// <param name="fullName">Full name of the type (no assembly information)</param>
|
|
/// <param name="isReflectionName"><c>true</c> if it's a reflection name, and nested
|
|
/// type names are separated by a <c>+</c> character. If <c>false</c>, nested type names
|
|
/// are separated by a <c>/</c> character.</param>
|
|
/// <returns>An existing <see cref="TypeDef"/> or <c>null</c> if it wasn't found.</returns>
|
|
public TypeDef Find(string fullName, bool isReflectionName) => TypeDefFinder.Find(fullName, isReflectionName);
|
|
|
|
/// <summary>
|
|
/// Finds a <see cref="TypeDef"/>. Its scope (i.e., module or assembly) is ignored when
|
|
/// looking up the type. For speed, enable <see cref="EnableTypeDefFindCache"/> if possible
|
|
/// (read the documentation first).
|
|
/// </summary>
|
|
/// <param name="typeRef">The type ref</param>
|
|
/// <returns>An existing <see cref="TypeDef"/> or <c>null</c> if it wasn't found.</returns>
|
|
public TypeDef Find(TypeRef typeRef) => TypeDefFinder.Find(typeRef);
|
|
|
|
/// <summary>
|
|
/// Finds a <see cref="TypeDef"/>
|
|
/// </summary>
|
|
/// <param name="typeRef">The type</param>
|
|
/// <returns>A <see cref="TypeDef"/> or <c>null</c> if it wasn't found</returns>
|
|
public TypeDef Find(ITypeDefOrRef typeRef) {
|
|
if (typeRef is TypeDef td)
|
|
return td.Module == this ? td : null;
|
|
|
|
if (typeRef is TypeRef tr)
|
|
return Find(tr);
|
|
|
|
var ts = typeRef as TypeSpec;
|
|
if (ts is null)
|
|
return null;
|
|
var sig = ts.TypeSig as TypeDefOrRefSig;
|
|
if (sig is null)
|
|
return null;
|
|
|
|
td = sig.TypeDef;
|
|
if (td is not null)
|
|
return td.Module == this ? td : null;
|
|
|
|
tr = sig.TypeRef;
|
|
if (tr is not null)
|
|
return Find(tr);
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="ModuleContext"/> instance. There should normally only be one
|
|
/// instance shared by all <see cref="ModuleDef"/>s.
|
|
/// </summary>
|
|
/// <returns>A new <see cref="ModuleContext"/> instance</returns>
|
|
public static ModuleContext CreateModuleContext() {
|
|
var ctx = new ModuleContext();
|
|
var asmRes = new AssemblyResolver(ctx);
|
|
var res = new Resolver(asmRes);
|
|
ctx.AssemblyResolver = asmRes;
|
|
ctx.Resolver = res;
|
|
asmRes.DefaultModuleContext = ctx;
|
|
return ctx;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load everything in this module. All types, fields, asm refs, etc are loaded, all their
|
|
/// properties are read to make sure everything is cached.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token or <c>null</c></param>
|
|
public virtual void LoadEverything(ICancellationToken cancellationToken = null) => ModuleLoader.LoadAll(this, cancellationToken);
|
|
|
|
/// <inheritdoc/>
|
|
public override string ToString() => FullName;
|
|
|
|
/// <summary>
|
|
/// Resolves a token
|
|
/// </summary>
|
|
/// <param name="mdToken">The metadata token</param>
|
|
/// <returns>A <see cref="IMDTokenProvider"/> or <c>null</c> if <paramref name="mdToken"/> is invalid</returns>
|
|
public IMDTokenProvider ResolveToken(MDToken mdToken) => ResolveToken(mdToken.Raw, new GenericParamContext());
|
|
|
|
/// <summary>
|
|
/// Resolves a token
|
|
/// </summary>
|
|
/// <param name="mdToken">The metadata token</param>
|
|
/// <param name="gpContext">Generic parameter context</param>
|
|
/// <returns>A <see cref="IMDTokenProvider"/> or <c>null</c> if <paramref name="mdToken"/> is invalid</returns>
|
|
public IMDTokenProvider ResolveToken(MDToken mdToken, GenericParamContext gpContext) => ResolveToken(mdToken.Raw, gpContext);
|
|
|
|
/// <summary>
|
|
/// Resolves a token
|
|
/// </summary>
|
|
/// <param name="token">The metadata token</param>
|
|
/// <returns>A <see cref="IMDTokenProvider"/> or <c>null</c> if <paramref name="token"/> is invalid</returns>
|
|
public IMDTokenProvider ResolveToken(int token) => ResolveToken((uint)token, new GenericParamContext());
|
|
|
|
/// <summary>
|
|
/// Resolves a token
|
|
/// </summary>
|
|
/// <param name="token">The metadata token</param>
|
|
/// <param name="gpContext">Generic parameter context</param>
|
|
/// <returns>A <see cref="IMDTokenProvider"/> or <c>null</c> if <paramref name="token"/> is invalid</returns>
|
|
public IMDTokenProvider ResolveToken(int token, GenericParamContext gpContext) => ResolveToken((uint)token, gpContext);
|
|
|
|
/// <summary>
|
|
/// Resolves a token
|
|
/// </summary>
|
|
/// <param name="token">The metadata token</param>
|
|
/// <returns>A <see cref="IMDTokenProvider"/> or <c>null</c> if <paramref name="token"/> is invalid</returns>
|
|
public IMDTokenProvider ResolveToken(uint token) => ResolveToken(token, new GenericParamContext());
|
|
|
|
/// <summary>
|
|
/// Resolves a token
|
|
/// </summary>
|
|
/// <param name="token">The metadata token</param>
|
|
/// <param name="gpContext">Generic parameter context</param>
|
|
/// <returns>A <see cref="IMDTokenProvider"/> or <c>null</c> if <paramref name="token"/> is invalid</returns>
|
|
public virtual IMDTokenProvider ResolveToken(uint token, GenericParamContext gpContext) => null;
|
|
|
|
/// <summary>
|
|
/// Gets all <see cref="AssemblyRef"/>s
|
|
/// </summary>
|
|
public IEnumerable<AssemblyRef> GetAssemblyRefs() {
|
|
for (uint rid = 1; ; rid++) {
|
|
var asmRef = ResolveToken(new MDToken(Table.AssemblyRef, rid).Raw) as AssemblyRef;
|
|
if (asmRef is null)
|
|
break;
|
|
yield return asmRef;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all <see cref="ModuleRef"/>s
|
|
/// </summary>
|
|
public IEnumerable<ModuleRef> GetModuleRefs() {
|
|
for (uint rid = 1; ; rid++) {
|
|
var modRef = ResolveToken(new MDToken(Table.ModuleRef, rid).Raw) as ModuleRef;
|
|
if (modRef is null)
|
|
break;
|
|
yield return modRef;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all <see cref="MemberRef"/>s. <see cref="MemberRef"/>s with generic parameters
|
|
/// aren't cached and a new copy is always returned.
|
|
/// </summary>
|
|
public IEnumerable<MemberRef> GetMemberRefs() => GetMemberRefs(new GenericParamContext());
|
|
|
|
/// <summary>
|
|
/// Gets all <see cref="MemberRef"/>s. <see cref="MemberRef"/>s with generic parameters
|
|
/// aren't cached and a new copy is always returned.
|
|
/// </summary>
|
|
/// <param name="gpContext">Generic parameter context</param>
|
|
public IEnumerable<MemberRef> GetMemberRefs(GenericParamContext gpContext) {
|
|
for (uint rid = 1; ; rid++) {
|
|
var mr = ResolveToken(new MDToken(Table.MemberRef, rid).Raw, gpContext) as MemberRef;
|
|
if (mr is null)
|
|
break;
|
|
yield return mr;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all <see cref="TypeRef"/>s
|
|
/// </summary>
|
|
public IEnumerable<TypeRef> GetTypeRefs() {
|
|
for (uint rid = 1; ; rid++) {
|
|
var mr = ResolveToken(new MDToken(Table.TypeRef, rid).Raw) as TypeRef;
|
|
if (mr is null)
|
|
break;
|
|
yield return mr;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds an assembly reference by name. If there's more than one, pick the one with
|
|
/// the greatest version number.
|
|
/// </summary>
|
|
/// <param name="simpleName">Simple name of assembly (eg. "mscorlib")</param>
|
|
/// <returns>The found <see cref="AssemblyRef"/> or <c>null</c> if there's no such
|
|
/// assembly reference.</returns>
|
|
public AssemblyRef GetAssemblyRef(UTF8String simpleName) {
|
|
AssemblyRef found = null;
|
|
foreach (var asmRef in GetAssemblyRefs()) {
|
|
if (asmRef.Name != simpleName)
|
|
continue;
|
|
if (IsGreaterAssemblyRefVersion(found, asmRef))
|
|
found = asmRef;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compare asm refs' version
|
|
/// </summary>
|
|
/// <param name="found">First asm ref</param>
|
|
/// <param name="newOne">New asm ref</param>
|
|
/// <returns></returns>
|
|
protected static bool IsGreaterAssemblyRefVersion(AssemblyRef found, AssemblyRef newOne) {
|
|
if (found is null)
|
|
return true;
|
|
var foundVer = found.Version;
|
|
var newVer = newOne.Version;
|
|
return foundVer is null || (newVer is not null && newVer >= foundVer);
|
|
}
|
|
|
|
ITypeDefOrRef ISignatureReaderHelper.ResolveTypeDefOrRef(uint codedToken, GenericParamContext gpContext) {
|
|
if (!CodedToken.TypeDefOrRef.Decode(codedToken, out uint token))
|
|
return null;
|
|
return ResolveToken(token) as ITypeDefOrRef;
|
|
}
|
|
|
|
TypeSig ISignatureReaderHelper.ConvertRTInternalAddress(IntPtr address) => null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A Module row created by the user and not present in the original .NET file
|
|
/// </summary>
|
|
public class ModuleDefUser : ModuleDef {
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
public ModuleDefUser()
|
|
: this(null, null) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <remarks><see cref="ModuleDef.Mvid"/> is initialized to a random <see cref="Guid"/></remarks>
|
|
/// <param name="name">Module nam</param>
|
|
public ModuleDefUser(UTF8String name)
|
|
: this(name, Guid.NewGuid()) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="name">Module name</param>
|
|
/// <param name="mvid">Module version ID</param>
|
|
public ModuleDefUser(UTF8String name, Guid? mvid)
|
|
: this(name, mvid, null) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="name">Module name</param>
|
|
/// <param name="mvid">Module version ID</param>
|
|
/// <param name="corLibAssemblyRef">Corlib assembly ref or <c>null</c></param>
|
|
public ModuleDefUser(UTF8String name, Guid? mvid, AssemblyRef corLibAssemblyRef) {
|
|
Kind = ModuleKind.Windows;
|
|
Characteristics = DefaultCharacteristics;
|
|
DllCharacteristics = DefaultDllCharacteristics;
|
|
RuntimeVersion = MDHeaderRuntimeVersion.MS_CLR_20;
|
|
Machine = Machine.I386;
|
|
cor20HeaderFlags = (int)ComImageFlags.ILOnly;
|
|
Cor20HeaderRuntimeVersion = 0x00020005; // .NET Framework 2.0 or later should use 2.5
|
|
TablesHeaderVersion = 0x0200; // .NET Framework 2.0 or later should use 2.0
|
|
types = new LazyList<TypeDef>(this);
|
|
exportedTypes = new LazyList<ExportedType>();
|
|
resources = new ResourceCollection();
|
|
corLibTypes = new CorLibTypes(this, corLibAssemblyRef);
|
|
types = new LazyList<TypeDef>(this);
|
|
this.name = name;
|
|
this.mvid = mvid;
|
|
types.Add(CreateModuleType());
|
|
UpdateRowId(this);
|
|
}
|
|
|
|
TypeDef CreateModuleType() {
|
|
var type = UpdateRowId(new TypeDefUser(UTF8String.Empty, "<Module>", null));
|
|
type.Attributes = TypeAttributes.NotPublic | TypeAttributes.AutoLayout | TypeAttributes.Class | TypeAttributes.AnsiClass;
|
|
return type;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Created from a row in the Module table
|
|
/// </summary>
|
|
public class ModuleDefMD2 : ModuleDef, IMDTokenProviderMD {
|
|
/// <summary>The module where this instance is located</summary>
|
|
readonly ModuleDefMD readerModule;
|
|
|
|
readonly uint origRid;
|
|
|
|
/// <inheritdoc/>
|
|
public uint OrigRid => origRid;
|
|
|
|
/// <inheritdoc/>
|
|
protected override void InitializeCustomAttributes() {
|
|
var list = readerModule.Metadata.GetCustomAttributeRidList(Table.Module, origRid);
|
|
var tmp = new CustomAttributeCollection(list.Count, list, (list2, index) => readerModule.ReadCustomAttribute(list[index]));
|
|
Interlocked.CompareExchange(ref customAttributes, tmp, null);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void InitializeCustomDebugInfos() {
|
|
var list = new List<PdbCustomDebugInfo>();
|
|
readerModule.InitializeCustomDebugInfos(new MDToken(MDToken.Table, origRid), new GenericParamContext(), list);
|
|
Interlocked.CompareExchange(ref customDebugInfos, list, null);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override RVA GetNativeEntryPoint_NoLock() => readerModule.GetNativeEntryPoint();
|
|
|
|
/// <inheritdoc/>
|
|
protected override IManagedEntryPoint GetManagedEntryPoint_NoLock() => readerModule.GetManagedEntryPoint();
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="readerModule">The module which contains this <c>Module</c> row</param>
|
|
/// <param name="rid">Row ID</param>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="readerModule"/> is <c>null</c></exception>
|
|
/// <exception cref="ArgumentException">If <paramref name="rid"/> is invalid</exception>
|
|
internal ModuleDefMD2(ModuleDefMD readerModule, uint rid) {
|
|
if (rid == 1 && readerModule is null)
|
|
readerModule = (ModuleDefMD)this;
|
|
#if DEBUG
|
|
if (readerModule is null)
|
|
throw new ArgumentNullException("readerModule");
|
|
if (rid != 1 && readerModule.TablesStream.ModuleTable.IsInvalidRID(rid))
|
|
throw new BadImageFormatException($"Module rid {rid} does not exist");
|
|
#endif
|
|
origRid = rid;
|
|
this.rid = rid;
|
|
this.readerModule = readerModule;
|
|
if (rid != 1) {
|
|
Kind = ModuleKind.Windows;
|
|
Characteristics = DefaultCharacteristics;
|
|
DllCharacteristics = DefaultDllCharacteristics;
|
|
RuntimeVersion = MDHeaderRuntimeVersion.MS_CLR_20;
|
|
Machine = Machine.I386;
|
|
cor20HeaderFlags = (int)ComImageFlags.ILOnly;
|
|
Cor20HeaderRuntimeVersion = 0x00020005; // .NET Framework 2.0 or later should use 2.5
|
|
TablesHeaderVersion = 0x0200; // .NET Framework 2.0 or later should use 2.0
|
|
corLibTypes = new CorLibTypes(this);
|
|
location = string.Empty;
|
|
InitializeFromRawRow();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize fields from the raw <c>Module</c> row
|
|
/// </summary>
|
|
protected void InitializeFromRawRow() {
|
|
bool b = readerModule.TablesStream.TryReadModuleRow(origRid, out var row);
|
|
Debug.Assert(b);
|
|
generation = row.Generation;
|
|
mvid = readerModule.GuidStream.Read(row.Mvid);
|
|
encId = readerModule.GuidStream.Read(row.EncId);
|
|
encBaseId = readerModule.GuidStream.Read(row.EncBaseId);
|
|
name = readerModule.StringsStream.ReadNoNull(row.Name);
|
|
if (origRid == 1)
|
|
assembly = readerModule.ResolveAssembly(origRid);
|
|
}
|
|
}
|
|
}
|