├── MetadataLocator.Test.CLR20.x64
├── Program.cs
└── MetadataLocator.Test.CLR20.x64.csproj
├── MetadataLocator.Test.CLR20.x86
├── Program.cs
└── MetadataLocator.Test.CLR20.x86.csproj
├── MetadataLocator.Test.CLR40.x64
├── Program.cs
└── MetadataLocator.Test.CLR40.x64.csproj
├── MetadataLocator.Test.CLR40.x86
├── Program.cs
└── MetadataLocator.Test.CLR40.x86.csproj
├── MetadataLocator.Test.CoreCLR31.x64
├── Program.cs
└── MetadataLocator.Test.CoreCLR31.x64.csproj
├── MetadataLocator.Test.CoreCLR31.x86
├── Program.cs
└── MetadataLocator.Test.CoreCLR31.x86.csproj
├── MetadataLocator.Test.CoreCLR60.x64
├── Program.cs
└── MetadataLocator.Test.CoreCLR60.x64.csproj
├── MetadataLocator.Test.CoreCLR60.x86
├── Program.cs
└── MetadataLocator.Test.CoreCLR60.x86.csproj
├── Images
└── locate-table-stream.png
├── MetadataLocator.Test.CLR.props
├── MetadataLocator
├── System
│ ├── HandleProcessCorruptedStateExceptionsAttribute.cs
│ ├── Array2.cs
│ ├── NRT_Helpers.cs
│ └── NRT.cs
├── MetadataLocator.csproj
├── ReflectionHelpers.cs
├── PEInfo.cs
├── RuntimeEnvironment.cs
├── MetadataInfo.cs
├── MetadataImport.cs
├── Memory.cs
├── Utils.cs
├── PEInfoImpl.cs
├── TestAssemblyManager.cs
├── RuntimeDefinitions.cs
└── MetadataInfoImpl.cs
├── MetadataLocator.Test
├── System
│ ├── Array2.cs
│ ├── NRT_Helpers.cs
│ └── NRT.cs
├── MetadataImportTests.cs
├── MetadataLocator.Test.csproj
├── Assert.cs
├── Utils.cs
├── RuntimeDefinitionTests.cs
└── TestDriver.cs
├── MetadataLocator.Test.CoreCLR.props
├── MetadataLocator.Test.props
├── appveyor.yml
├── README.md
├── LICENSE
├── .gitattributes
├── MetadataLocator.sln
├── .gitignore
└── .editorconfig
/MetadataLocator.Test.CLR20.x64/Program.cs:
--------------------------------------------------------------------------------
1 | using MetadataLocator.Test;
2 |
3 | TestDriver.Test();
4 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CLR20.x86/Program.cs:
--------------------------------------------------------------------------------
1 | using MetadataLocator.Test;
2 |
3 | TestDriver.Test();
4 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CLR40.x64/Program.cs:
--------------------------------------------------------------------------------
1 | using MetadataLocator.Test;
2 |
3 | TestDriver.Test();
4 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CLR40.x86/Program.cs:
--------------------------------------------------------------------------------
1 | using MetadataLocator.Test;
2 |
3 | TestDriver.Test();
4 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR31.x64/Program.cs:
--------------------------------------------------------------------------------
1 | using MetadataLocator.Test;
2 |
3 | TestDriver.Test();
4 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR31.x86/Program.cs:
--------------------------------------------------------------------------------
1 | using MetadataLocator.Test;
2 |
3 | TestDriver.Test();
4 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR60.x64/Program.cs:
--------------------------------------------------------------------------------
1 | using MetadataLocator.Test;
2 |
3 | TestDriver.Test();
4 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR60.x86/Program.cs:
--------------------------------------------------------------------------------
1 | using MetadataLocator.Test;
2 |
3 | TestDriver.Test();
4 |
--------------------------------------------------------------------------------
/Images/locate-table-stream.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wwh1004/MetadataLocator/HEAD/Images/locate-table-stream.png
--------------------------------------------------------------------------------
/MetadataLocator.Test.CLR20.x64/MetadataLocator.Test.CLR20.x64.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net35
4 | x64
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CLR20.x86/MetadataLocator.Test.CLR20.x86.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net35
4 | x86
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CLR40.x64/MetadataLocator.Test.CLR40.x64.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net40
4 | x64
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CLR40.x86/MetadataLocator.Test.CLR40.x86.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net40
4 | x86
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CLR.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR60.x64/MetadataLocator.Test.CoreCLR60.x64.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | x64
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR60.x86/MetadataLocator.Test.CoreCLR60.x86.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | x86
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR31.x64/MetadataLocator.Test.CoreCLR31.x64.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 | x64
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR31.x86/MetadataLocator.Test.CoreCLR31.x86.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 | x86
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetadataLocator/System/HandleProcessCorruptedStateExceptionsAttribute.cs:
--------------------------------------------------------------------------------
1 | #if !NET40_OR_GREATER
2 | namespace System.Runtime.ExceptionServices;
3 |
4 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
5 | sealed class HandleProcessCorruptedStateExceptionsAttribute : Attribute {
6 | }
7 | #endif
8 |
--------------------------------------------------------------------------------
/MetadataLocator/System/Array2.cs:
--------------------------------------------------------------------------------
1 | namespace System;
2 |
3 | static class Array2 {
4 | public static T[] Empty() {
5 | return EmptyArray.Value;
6 | }
7 |
8 | static class EmptyArray {
9 | #pragma warning disable CA1825
10 | public static readonly T[] Value = new T[0];
11 | #pragma warning restore CA1825
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/MetadataLocator.Test/System/Array2.cs:
--------------------------------------------------------------------------------
1 | namespace System;
2 |
3 | static class Array2 {
4 | public static T[] Empty() {
5 | return EmptyArray.Value;
6 | }
7 |
8 | static class EmptyArray {
9 | #pragma warning disable CA1825
10 | public static readonly T[] Value = new T[0];
11 | #pragma warning restore CA1825
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.CoreCLR.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | NU1702
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MetadataLocator/System/NRT_Helpers.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace System.Diagnostics;
4 |
5 | static class Debug2 {
6 | [Conditional("DEBUG")]
7 | public static void Assert([DoesNotReturnIf(false)] bool condition) {
8 | Debug.Assert(condition);
9 | }
10 |
11 | [Conditional("DEBUG")]
12 | public static void Assert([DoesNotReturnIf(false)] bool condition, string? message) {
13 | Debug.Assert(condition, message);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/MetadataLocator.Test/MetadataImportTests.cs:
--------------------------------------------------------------------------------
1 | namespace MetadataLocator.Test;
2 |
3 | static unsafe class MetadataImportTests {
4 | public static void Test() {
5 | var module = typeof(MetadataImportTests).Module;
6 | var metadataImport = MetadataImport.Create(module);
7 | Assert.IsTrue(metadataImport is not null);
8 | nuint pVersion = metadataImport.GetVersionString();
9 | var version = new string((sbyte*)pVersion);
10 | Assert.IsTrue(version == "v2.0.50727");
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/MetadataLocator.Test.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(ProjectName)
4 | 2.0.0.0
5 | Copyright © 2019-2022 Wwh
6 |
7 |
8 | true
9 | 10.0
10 | enable
11 | ..\bin\$(Configuration)
12 | false
13 |
14 |
15 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | image: Visual Studio 2022
3 | configuration: Release
4 | platform: Any CPU
5 | before_build:
6 | - cmd: appveyor-retry nuget restore
7 | build:
8 | project: MetadataLocator.sln
9 | verbosity: normal
10 | artifacts:
11 | - path: bin\Release
12 | name: MetadataLocator
13 | deploy:
14 | - provider: GitHub
15 | tag: $(APPVEYOR_REPO_TAG_NAME)
16 | release: MetadataLocator
17 | auth_token:
18 | secure: +8UJ1C312inNq+80I8WST34vPMrCylnmTx+9rmuIh1qnsArA5x2b8yc+kcwkXmQC
19 | on:
20 | APPVEYOR_REPO_TAG: true
--------------------------------------------------------------------------------
/MetadataLocator.Test/MetadataLocator.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net35
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MetadataLocator
2 | Locate the address of .NET metadata by CLR to anti anti dump
3 |
4 | 
5 | Locate the table stream under the full protection of ConfuserEx (include AntiDump protection)
6 |
7 | ## Requirements
8 | - Any .NET Framework
9 |
10 | ## Downloads
11 | GitHub: [Latest release](https://github.com/wwh1004/MetadataLocator/releases/latest/download/MetadataLocator.zip)
12 |
13 | AppVeyor: [](https://ci.appveyor.com/project/wwh1004/metadatalocator)
14 |
--------------------------------------------------------------------------------
/MetadataLocator/MetadataLocator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(ProjectName)
4 | 2.0.0.1
5 | Copyright © 2019-2022 Wwh
6 |
7 |
8 | net35
9 | true
10 | 10.0
11 | enable
12 | ..\bin\$(Configuration)
13 | true
14 | false
15 |
16 |
17 |
--------------------------------------------------------------------------------
/MetadataLocator.Test/Assert.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 |
4 | namespace MetadataLocator.Test;
5 |
6 | sealed class AssertFailedException : Exception {
7 | public AssertFailedException() {
8 | }
9 |
10 | public AssertFailedException(string message) : base(message) {
11 | }
12 | }
13 |
14 | static class Assert {
15 | public static void IsTrue([DoesNotReturnIf(false)] bool condition) {
16 | if (!condition)
17 | throw new AssertFailedException();
18 | }
19 |
20 | public static void IsTrue([DoesNotReturnIf(false)] bool condition, string message) {
21 | if (!condition)
22 | throw new AssertFailedException(message);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/MetadataLocator.Test/System/NRT_Helpers.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace System {
4 | static class string2 {
5 | public static bool IsNullOrEmpty([NotNullWhen(false)] string? value) {
6 | return string.IsNullOrEmpty(value);
7 | }
8 | }
9 | }
10 |
11 | namespace System.Diagnostics {
12 | static class Debug2 {
13 | [Conditional("DEBUG")]
14 | public static void Assert([DoesNotReturnIf(false)] bool condition) {
15 | Debug.Assert(condition);
16 | }
17 |
18 | [Conditional("DEBUG")]
19 | public static void Assert([DoesNotReturnIf(false)] bool condition, string? message) {
20 | Debug.Assert(condition, message);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/MetadataLocator.Test/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection.Emit;
4 |
5 | namespace MetadataLocator.Test;
6 |
7 | static class Utils {
8 | static readonly Dictionary sizes = new();
9 |
10 | public static uint SizeOf(Type type) {
11 | if (!sizes.TryGetValue(type, out uint size)) {
12 | size = GetSize(type);
13 | sizes.Add(type, size);
14 | }
15 | return size;
16 | }
17 |
18 | static uint GetSize(Type type) {
19 | var dm = new DynamicMethod($"SizeOf_{type.Name}", typeof(int), Type.EmptyTypes, typeof(Utils).Module, true);
20 | var il = dm.GetILGenerator();
21 | il.Emit(OpCodes.Sizeof, type);
22 | il.Emit(OpCodes.Ret);
23 | return (uint)(int)dm.Invoke(null, null);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/MetadataLocator/System/NRT.cs:
--------------------------------------------------------------------------------
1 | #if NETFRAMEWORK || NETSTANDARD2_0
2 | namespace System.Diagnostics.CodeAnalysis;
3 |
4 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
5 | sealed class NotNullWhenAttribute : Attribute {
6 | public bool ReturnValue { get; }
7 |
8 | public NotNullWhenAttribute(bool returnValue) {
9 | ReturnValue = returnValue;
10 | }
11 | }
12 |
13 | [AttributeUsage(AttributeTargets.Method, Inherited = false)]
14 | sealed class DoesNotReturnAttribute : Attribute {
15 | public DoesNotReturnAttribute() {
16 | }
17 | }
18 |
19 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
20 | sealed class DoesNotReturnIfAttribute : Attribute {
21 | public bool ParameterValue { get; }
22 |
23 | public DoesNotReturnIfAttribute(bool parameterValue) {
24 | ParameterValue = parameterValue;
25 | }
26 | }
27 | #endif
28 |
--------------------------------------------------------------------------------
/MetadataLocator.Test/System/NRT.cs:
--------------------------------------------------------------------------------
1 | #if NETFRAMEWORK || NETSTANDARD2_0
2 | namespace System.Diagnostics.CodeAnalysis;
3 |
4 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
5 | sealed class NotNullWhenAttribute : Attribute {
6 | public bool ReturnValue { get; }
7 |
8 | public NotNullWhenAttribute(bool returnValue) {
9 | ReturnValue = returnValue;
10 | }
11 | }
12 |
13 | [AttributeUsage(AttributeTargets.Method, Inherited = false)]
14 | sealed class DoesNotReturnAttribute : Attribute {
15 | public DoesNotReturnAttribute() {
16 | }
17 | }
18 |
19 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
20 | sealed class DoesNotReturnIfAttribute : Attribute {
21 | public bool ParameterValue { get; }
22 |
23 | public DoesNotReturnIfAttribute(bool parameterValue) {
24 | ParameterValue = parameterValue;
25 | }
26 | }
27 | #endif
28 |
--------------------------------------------------------------------------------
/MetadataLocator/ReflectionHelpers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace MetadataLocator;
6 |
7 | static class ReflectionHelpers {
8 | static readonly FieldInfo moduleHandleField = GetModuleHandleField();
9 |
10 | static FieldInfo GetModuleHandleField() {
11 | if (RuntimeEnvironment.Version < RuntimeVersion.Fx40)
12 | return typeof(ModuleHandle).GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance);
13 | return typeof(object).Module.GetType("System.Reflection.RuntimeModule").GetField("m_pData", BindingFlags.NonPublic | BindingFlags.Instance);
14 | }
15 |
16 | public static nuint GetModuleHandle(Module module) {
17 | if (RuntimeEnvironment.Version < RuntimeVersion.Fx40)
18 | return (nuint)(nint)moduleHandleField.GetValue(module.ModuleHandle);
19 | return (nuint)(nint)moduleHandleField.GetValue(module);
20 | }
21 |
22 | public static nuint GetNativeModuleHandle(Module module) {
23 | if (module is null)
24 | throw new ArgumentNullException(nameof(module));
25 |
26 | return (nuint)(nint)Marshal.GetHINSTANCE(module);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 wwh1004
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MetadataLocator/PEInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace MetadataLocator;
4 |
5 | ///
6 | /// CLR internal image layout
7 | ///
8 | public sealed class PEImageLayout {
9 | ///
10 | /// Determine if current instance is invalid
11 | ///
12 | public bool IsInvalid => ImageBase == 0;
13 |
14 | ///
15 | /// Image base address
16 | ///
17 | public nuint ImageBase;
18 |
19 | ///
20 | /// Image size (size of file on disk, as opposed to OptionalHeaders.SizeOfImage)
21 | ///
22 | public uint ImageSize;
23 |
24 | ///
25 | /// Address of
26 | ///
27 | public nuint CorHeaderAddress;
28 | }
29 |
30 | ///
31 | /// .NET PE Info
32 | ///
33 | public sealed class PEInfo {
34 | ///
35 | /// Determine if current instance is invalid
36 | ///
37 | public bool IsInvalid => LoadedLayout.IsInvalid;
38 |
39 | ///
40 | /// Image file path
41 | ///
42 | public string FilePath = string.Empty;
43 |
44 | ///
45 | /// If image is loaded in memory
46 | ///
47 | public bool InMemory => string.IsNullOrEmpty(FilePath);
48 |
49 | ///
50 | /// Flat image layout, maybe empty (Assembly.Load(byte[]))
51 | ///
52 | public PEImageLayout FlatLayout = new();
53 |
54 | ///
55 | /// Mapped image layout, maybe empty (Assembly.LoadFile)
56 | ///
57 | public PEImageLayout MappedLayout = new();
58 |
59 | ///
60 | /// Loaded image layout, not empty (Assembly.LoadFile)
61 | ///
62 | public PEImageLayout LoadedLayout = new();
63 |
64 | ///
65 | /// Get the .NET PE info of a module
66 | ///
67 | ///
68 | ///
69 | public static PEInfo Create(Module module) {
70 | return PEInfoImpl.GetPEInfo(module);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/MetadataLocator.Test/RuntimeDefinitionTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using static MetadataLocator.RuntimeDefinitions;
4 |
5 | namespace MetadataLocator.Test;
6 |
7 | static unsafe class RuntimeDefinitionTests {
8 | readonly struct ArchSize {
9 | public readonly ushort X86;
10 | public readonly ushort X64;
11 |
12 | public ArchSize(ushort x86, ushort x64) {
13 | X86 = x86;
14 | X64 = x64;
15 | }
16 | }
17 |
18 | static readonly Dictionary sizeMap = new() {
19 | [typeof(SString)] = new(0x10, 0x18),
20 | [typeof(Crst)] = new(0x1c, 0x30),
21 | [typeof(PEDecoder)] = new(0x18, 0x28),
22 | [typeof(StgPoolReadOnly)] = new(0x18, 0x28),
23 | [typeof(StgBlobPoolReadOnly)] = new(0x18, 0x28),
24 | [typeof(TableRO)] = new(0x04, 0x08),
25 | [typeof(TableROs)] = new(0x00b4, 0x0168),
26 | [typeof(StringHeapRO)] = new(0x18, 0x28),
27 | [typeof(BlobHeapRO)] = new(0x18, 0x28),
28 | [typeof(GuidHeapRO)] = new(0x18, 0x28),
29 | [typeof(CMiniMdSchemaBase)] = new(0x18, 0x18),
30 | [typeof(CMiniMdSchema)] = new(0xd0, 0xd0),
31 | [typeof(CMiniColDef)] = new(0x03, 0x03),
32 | [typeof(CMiniTableDef)] = new(0x08, 0x10),
33 | [typeof(CMiniTableDefs)] = new(0x0168, 0x02d0),
34 | [typeof(CMiniMdBase_20)] = new(0x0250, 0x03c0),
35 | [typeof(CMiniMdBase_40)] = new(0x0258, 0x03c0),
36 | [typeof(CMiniMd_20)] = new(0x0368, 0x05c8),
37 | [typeof(CMiniMd_40)] = new(0x0370, 0x05c8),
38 | [typeof(CLiteWeightStgdb_CMiniMd_20)] = new(0x0370, 0x05d8),
39 | [typeof(CLiteWeightStgdb_CMiniMd_40)] = new(0x0378, 0x05d8),
40 | [typeof(MDInternalRO_20)] = new(0x0378, 0x05e0),
41 | [typeof(MDInternalRO_40)] = new(0x0380, 0x05e0),
42 | [typeof(MDInternalRO_45)] = new(0x0380, 0x05e8),
43 | };
44 |
45 | public static void VerifySize() {
46 | foreach (var sizeEntry in sizeMap) {
47 | uint actualSize = Utils.SizeOf(sizeEntry.Key);
48 | uint expectedSize = sizeof(nuint) == 4 ? sizeEntry.Value.X86 : sizeEntry.Value.X64;
49 | Assert.IsTrue(actualSize == expectedSize, $"Expected size 0x{expectedSize:X} bytes but got 0x{actualSize:X} bytes for {sizeEntry.Key.Name}");
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/MetadataLocator/RuntimeEnvironment.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 | using System.Text;
5 |
6 | namespace MetadataLocator;
7 |
8 | enum RuntimeFlavor {
9 | ///
10 | /// .NET Framework 1.0 ~ 4.8
11 | ///
12 | Framework,
13 |
14 | ///
15 | /// .NET Core 1.0 ~ 3.1 and .NET 5.0 +
16 | ///
17 | Core
18 | }
19 |
20 | enum RuntimeVersion {
21 | Fx20,
22 | Fx40,
23 | Fx45,
24 | Fx453,
25 | Core10,
26 | Core30,
27 | Core31,
28 | Core50,
29 | Core60,
30 | }
31 |
32 | static class RuntimeEnvironment {
33 | static readonly Version fx45 = new(4, 0, 30319, 17000);
34 | static readonly Version fx453 = new(4, 5, 0, 0); // .NET 4.6 Preview
35 |
36 | public static RuntimeFlavor Flavor { get; } = GetRuntimeFlavor();
37 |
38 | public static RuntimeVersion Version { get; } = GetRuntimeVersion();
39 |
40 | static RuntimeFlavor GetRuntimeFlavor() {
41 | switch (typeof(object).Module.ScopeName) {
42 | case "CommonLanguageRuntimeLibrary": return RuntimeFlavor.Framework;
43 | case "System.Private.CoreLib.dll": return RuntimeFlavor.Core;
44 | default: throw new NotSupportedException();
45 | }
46 | }
47 |
48 | static RuntimeVersion GetRuntimeVersion() {
49 | var version = Environment.Version;
50 | int major = version.Major;
51 | switch (Flavor) {
52 | case RuntimeFlavor.Framework: {
53 | if (major == 4) {
54 | var path = new StringBuilder(MAX_PATH);
55 | if (!GetModuleFileName(GetModuleHandle("clr.dll"), path, MAX_PATH))
56 | break;
57 | var fileVersion = GetFileVersion(path.ToString());
58 | if (fileVersion >= fx453)
59 | return RuntimeVersion.Fx453;
60 | if (fileVersion >= fx45)
61 | return RuntimeVersion.Fx45;
62 | return RuntimeVersion.Fx40;
63 | }
64 | else if (major == 2) {
65 | return RuntimeVersion.Fx20;
66 | }
67 | break;
68 | }
69 | case RuntimeFlavor.Core: {
70 | if (major == 4)
71 | return RuntimeVersion.Core10;
72 | // Improve .NET Core version APIs: https://github.com/dotnet/runtime/issues/28701
73 | // Environment.Version works fine since .NET Core v3.0
74 | int minor = version.Minor;
75 | Debug2.Assert(major <= 6, "Update RuntimeDefinitions if need");
76 | if (major >= 6)
77 | return RuntimeVersion.Core60;
78 | if (major >= 5)
79 | return RuntimeVersion.Core50;
80 | if (major >= 3) {
81 | if (minor >= 1)
82 | return RuntimeVersion.Core31;
83 | return RuntimeVersion.Core30;
84 | }
85 | break;
86 | }
87 | }
88 | Debug2.Assert(false);
89 | throw new NotSupportedException();
90 | }
91 |
92 | static Version GetFileVersion(string filePath) {
93 | var versionInfo = FileVersionInfo.GetVersionInfo(filePath);
94 | return new Version(versionInfo.FileMajorPart, versionInfo.FileMinorPart, versionInfo.FileBuildPart, versionInfo.FilePrivatePart);
95 | }
96 |
97 | #region NativeMethods
98 | const ushort MAX_PATH = 260;
99 |
100 | [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true)]
101 | static extern nuint GetModuleHandle(string? lpModuleName);
102 |
103 | [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true)]
104 | [return: MarshalAs(UnmanagedType.Bool)]
105 | static extern bool GetModuleFileName(nuint hModule, StringBuilder lpFilename, uint nSize);
106 | #endregion
107 | }
108 |
--------------------------------------------------------------------------------
/MetadataLocator/MetadataInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace MetadataLocator;
5 |
6 | ///
7 | /// Metadata schema
8 | ///
9 | public sealed class MetadataSchema {
10 | ///
11 | /// Determine if current instance is invalid
12 | ///
13 | public bool IsInvalid => ValidMask == 0;
14 |
15 | ///
16 | public uint Reserved1;
17 |
18 | ///
19 | public byte MajorVersion;
20 |
21 | ///
22 | public byte MinorVersion;
23 |
24 | ///
25 | public byte Flags;
26 |
27 | ///
28 | public byte Log2Rid;
29 |
30 | ///
31 | public ulong ValidMask;
32 |
33 | ///
34 | public ulong SortedMask;
35 |
36 | ///
37 | /// Array length always equals to if not empty
38 | public uint[] RowCounts = Array2.Empty();
39 |
40 | ///
41 | public uint ExtraData;
42 | }
43 |
44 | ///
45 | /// Metadata stream info
46 | ///
47 | public abstract class MetadataStreamInfo {
48 | ///
49 | /// Determine if current instance is invalid
50 | ///
51 | public bool IsInvalid => Address == 0;
52 |
53 | ///
54 | /// Address of stream
55 | ///
56 | public nuint Address;
57 |
58 | ///
59 | /// Length of stream
60 | ///
61 | public uint Length;
62 | }
63 |
64 | ///
65 | /// Metadata table info (#~, #-)
66 | ///
67 | public sealed class MetadataTableInfo : MetadataStreamInfo {
68 | ///
69 | /// Is compressed table stream (#~)
70 | ///
71 | public bool IsCompressed;
72 |
73 | ///
74 | /// Table count, see and
75 | ///
76 | public uint TableCount;
77 |
78 | ///
79 | /// Size of each row
80 | ///
81 | /// Array length always equals to if not empty
82 | public uint[] RowSizes = Array2.Empty();
83 | }
84 |
85 | ///
86 | /// Metadata heap info (#Strings, #US, #GUID, #Blob)
87 | ///
88 | public sealed class MetadataHeapInfo : MetadataStreamInfo {
89 | }
90 |
91 | ///
92 | /// Metadata info
93 | ///
94 | public sealed class MetadataInfo {
95 | ///
96 | /// Determine if current instance is invalid
97 | ///
98 | public bool IsInvalid => MetadataAddress == 0;
99 |
100 | ///
101 | /// The instance of
102 | ///
103 | public MetadataImport MetadataImport = MetadataImport.Empty;
104 |
105 | ///
106 | /// Address of metadata
107 | ///
108 | public nuint MetadataAddress;
109 |
110 | ///
111 | /// Size of metadata
112 | ///
113 | /// Currently return 0x1c if table stream is uncompressed (aka #-)
114 | public uint MetadataSize;
115 |
116 | ///
117 | /// Metadata schema
118 | ///
119 | public MetadataSchema Schema = new();
120 |
121 | ///
122 | /// #~ or #- info
123 | ///
124 | public MetadataTableInfo TableStream = new();
125 |
126 | ///
127 | /// #Strings heap info
128 | ///
129 | public MetadataHeapInfo StringHeap = new();
130 |
131 | ///
132 | /// #US heap info
133 | ///
134 | public MetadataHeapInfo UserStringHeap = new();
135 |
136 | ///
137 | /// #GUID heap info
138 | ///
139 | public MetadataHeapInfo GuidHeap = new();
140 |
141 | ///
142 | /// #Blob heap info
143 | ///
144 | public MetadataHeapInfo BlobHeap = new();
145 |
146 | ///
147 | /// Get the metadata info of a module
148 | ///
149 | ///
150 | ///
151 | public static MetadataInfo Create(Module module) {
152 | return MetadataInfoImpl.GetMetadataInfo(module);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/MetadataLocator/MetadataImport.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using SR = System.Reflection;
4 |
5 | namespace MetadataLocator;
6 |
7 | ///
8 | /// IMDInternalImport function
9 | ///
10 | public enum MetadataImportFunction {
11 | ///
12 | /// IMDInternalImport.GetVersionString
13 | ///
14 | GetVersionString
15 | }
16 |
17 | ///
18 | /// IMDInternalImport info
19 | ///
20 | public sealed unsafe class MetadataImport {
21 | sealed class Profile {
22 | public readonly Guid IID;
23 | public readonly uint GetVersionString;
24 |
25 | public Profile(Guid iid, uint getVersionString) {
26 | IID = iid;
27 | GetVersionString = getVersionString;
28 | }
29 | }
30 |
31 | static readonly SR.MethodInfo getMetadataImport = typeof(ModuleHandle).GetMethod("_GetMetadataImport", SR.BindingFlags.NonPublic | SR.BindingFlags.Instance | SR.BindingFlags.Static);
32 | static readonly Profile[] profiles = {
33 | new(new(0xce0f34ed, 0xbbc6, 0x11d2, 0x94, 0x1e, 0x00, 0x00, 0xf8, 0x08, 0x34, 0x60), 102), // fx20 ~ core31
34 | new(new(0x1b119f60, 0xc507, 0x4024, 0xbb, 0x39, 0xf8, 0x22, 0x3f, 0xb3, 0xe1, 0xfd), 92) // core50+
35 | };
36 | static Profile? cachedProfile;
37 |
38 | // IMDInternalImport interface contains breaking change in this commit: https://github.com/dotnet/coreclr/commit/ef7767a3ba1c0a34b55cbd5496b799b17218ca14#diff-1a774ef53fa72817aedde61a45204358e0cd0a76272780558180dc27a2f85cb5L309
39 | // so we must call QueryInterface to judge which version IMDInternalImport we are using
40 |
41 | ///
42 | /// Empty instance
43 | ///
44 | public static readonly MetadataImport Empty = new(0);
45 |
46 | ///
47 | /// Determine if current instance is invalid
48 | ///
49 | public bool IsInvalid => This == 0;
50 |
51 | ///
52 | /// Instance of IMDInternalImport
53 | ///
54 | public nuint This { get; }
55 |
56 | ///
57 | /// Virtual function vtable
58 | ///
59 | public nuint Vfptr => *(nuint*)This;
60 |
61 | MetadataImport(nuint @this) {
62 | This = @this;
63 | }
64 |
65 | ///
66 | /// A wrapper for _GetMetadataImport to get instance of IMDInternalImport interface
67 | ///
68 | ///
69 | ///
70 | public static MetadataImport Create(SR.Module module) {
71 | if (module is null)
72 | throw new ArgumentNullException(nameof(module));
73 |
74 | nuint pMDImport = getMetadataImport.IsStatic
75 | ? (nuint)(nint)getMetadataImport.Invoke(null, new object[] { module })
76 | : (nuint)SR.Pointer.Unbox(getMetadataImport.Invoke(module.ModuleHandle, null));
77 | return pMDImport != 0 ? new MetadataImport(pMDImport) : Empty;
78 | }
79 |
80 | ///
81 | /// Get virtual function address
82 | ///
83 | ///
84 | ///
85 | ///
86 | public nuint GetFunction(MetadataImportFunction function) {
87 | var profile = GetProfile(This);
88 | switch (function) {
89 | case MetadataImportFunction.GetVersionString:
90 | return ((nuint*)Vfptr)[profile.GetVersionString];
91 | default:
92 | throw new NotSupportedException();
93 | }
94 | }
95 |
96 | ///
97 | /// Call IMDInternalImport.GetVersionString and return pVersion
98 | ///
99 | ///
100 | public nuint GetVersionString() {
101 | var getVersionString = (delegate* unmanaged[Stdcall])GetFunction(MetadataImportFunction.GetVersionString);
102 | int hr = getVersionString(This, out nuint pVersion);
103 | Debug2.Assert(hr >= 0);
104 | return pVersion;
105 | }
106 |
107 | static Profile GetProfile(nuint pMDImport) {
108 | Debug2.Assert(pMDImport != 0);
109 | if (cachedProfile is not null)
110 | return cachedProfile;
111 |
112 | var vfptr = *(nuint**)pMDImport;
113 | var queryInterface = (delegate* unmanaged[Stdcall])vfptr[0];
114 | var release = (delegate* unmanaged[Stdcall])vfptr[2];
115 | foreach (var profile in profiles) {
116 | int hr = queryInterface(pMDImport, profile.IID, out nuint pUnk);
117 | if (hr < 0)
118 | continue;
119 |
120 | Debug2.Assert(pUnk == pMDImport);
121 | release(pMDImport);
122 | cachedProfile = profile;
123 | return profile;
124 | }
125 |
126 | throw new NotSupportedException("Please update IMDInternalImport profiles");
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/MetadataLocator/Memory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.Runtime.ExceptionServices;
5 | using System.Runtime.InteropServices;
6 | using System.Text;
7 |
8 | namespace MetadataLocator;
9 |
10 | sealed class Pointer {
11 | public static readonly Pointer Empty = new(0, Array2.Empty());
12 |
13 | public bool IsEmpty => BaseAddress == 0 && Offsets.Count == 0;
14 |
15 | public nuint BaseAddress { get; set; }
16 |
17 | public IList Offsets { get; }
18 |
19 | public Pointer(IEnumerable offsets) {
20 | Offsets = new List(offsets);
21 | }
22 |
23 | public Pointer(nuint baseAddress, IEnumerable offsets) {
24 | BaseAddress = baseAddress;
25 | Offsets = new List(offsets);
26 | }
27 |
28 | public Pointer(Pointer pointer) {
29 | BaseAddress = pointer.BaseAddress;
30 | Offsets = new List(pointer.Offsets);
31 | }
32 |
33 | public void Add(uint offset) {
34 | Offsets.Add(offset);
35 | }
36 |
37 | public void Add(IEnumerable offsets) {
38 | if (offsets is null)
39 | throw new ArgumentNullException(nameof(offsets));
40 |
41 | foreach (uint offset in offsets)
42 | Offsets.Add(offset);
43 | }
44 | }
45 |
46 | static unsafe class Memory {
47 | [ThreadStatic]
48 | static readonly byte[] stringBuffer = new byte[4096];
49 |
50 | [HandleProcessCorruptedStateExceptions]
51 | public static bool TryToAddress(Pointer pointer, out nuint address) {
52 | address = 0;
53 | if (pointer is null)
54 | return false;
55 |
56 | try {
57 | address = 0;
58 | nuint newAddress = pointer.BaseAddress;
59 | var offsets = pointer.Offsets;
60 | if (offsets.Count > 0) {
61 | for (int i = 0; i < offsets.Count - 1; i++) {
62 | newAddress += offsets[i];
63 | if (!TryReadUIntPtr(newAddress, out newAddress))
64 | return false;
65 | }
66 | newAddress += offsets[offsets.Count - 1];
67 | }
68 | address = newAddress;
69 | return true;
70 | }
71 | catch {
72 | return false;
73 | }
74 | }
75 |
76 | [HandleProcessCorruptedStateExceptions]
77 | public static bool TryReadUInt32(nuint address, out uint value) {
78 | value = 0;
79 | if (address == 0)
80 | return false;
81 |
82 | if (IsBadReadPtr(address, 4))
83 | return false;
84 |
85 | try {
86 | value = *(uint*)address;
87 | return true;
88 | }
89 | catch {
90 | return false;
91 | }
92 | }
93 |
94 | [HandleProcessCorruptedStateExceptions]
95 | public static bool TryReadUInt64(nuint address, out ulong value) {
96 | value = 0;
97 | if (address == 0)
98 | return false;
99 |
100 | if (IsBadReadPtr(address, 8))
101 | return false;
102 |
103 | try {
104 | value = *(ulong*)address;
105 | return true;
106 | }
107 | catch {
108 | return false;
109 | }
110 | }
111 |
112 | [HandleProcessCorruptedStateExceptions]
113 | public static bool TryReadUIntPtr(nuint address, out nuint value) {
114 | value = 0;
115 | if (address == 0)
116 | return false;
117 |
118 | if (IsBadReadPtr(address, (uint)sizeof(nuint)))
119 | return false;
120 |
121 | try {
122 | value = *(nuint*)address;
123 | return true;
124 | }
125 | catch {
126 | value = 0;
127 | return false;
128 | }
129 | }
130 |
131 | [HandleProcessCorruptedStateExceptions]
132 | public static bool TryReadAnsiString(nuint address, [NotNullWhen(true)] out string? value) {
133 | value = null;
134 | if (address == 0)
135 | return false;
136 |
137 | if (IsBadReadPtr(address, 8))
138 | return false;
139 |
140 | try {
141 | value = new string((sbyte*)address);
142 | return true;
143 | }
144 | catch {
145 | return false;
146 | }
147 | }
148 |
149 | [HandleProcessCorruptedStateExceptions]
150 | public static bool TryReadUnicodeString(nuint address, [NotNullWhen(true)] out string? value) {
151 | value = null;
152 | if (address == 0)
153 | return false;
154 |
155 | if (IsBadReadPtr(address, 8))
156 | return false;
157 |
158 | try {
159 | value = new string((char*)address);
160 | return true;
161 | }
162 | catch {
163 | return false;
164 | }
165 | }
166 |
167 | [HandleProcessCorruptedStateExceptions]
168 | public static bool TryReadUtf8String(nuint address, [NotNullWhen(true)] out string? value) {
169 | value = null;
170 | if (address == 0)
171 | return false;
172 |
173 | if (IsBadReadPtr(address, 8))
174 | return false;
175 |
176 | try {
177 | uint i = 0;
178 | for (; *(byte*)(address + i) != 0; i++)
179 | stringBuffer[i] = *(byte*)(address + i);
180 | // TODO: assume no string larger than 4096 bytes
181 | stringBuffer[i] = 0;
182 | value = Encoding.UTF8.GetString(stringBuffer, 0, (int)i);
183 | return true;
184 | }
185 | catch {
186 | return false;
187 | }
188 | }
189 |
190 | [DllImport("kernel32.dll", SetLastError = true)]
191 | static extern bool IsBadReadPtr(nuint lp, nuint ucb);
192 | // TODO: In .NET Core, HandleProcessCorruptedStateExceptionsAttribute is invalid and we can't capture AccessViolationException no longer.
193 | // We should find a better way to check address readable.
194 | }
195 |
--------------------------------------------------------------------------------
/MetadataLocator/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.IO;
5 |
6 | namespace MetadataLocator;
7 |
8 | static unsafe class Utils {
9 | public static void Check(RuntimeDefinitions.Module* module, string moduleName) {
10 | Check(Memory.TryReadUIntPtr((nuint)module, out _));
11 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453) {
12 | nuint m_pSimpleName = ((RuntimeDefinitions.Module_453*)module)->m_pSimpleName;
13 | bool b = Memory.TryReadUtf8String(m_pSimpleName, out var simpleName) && simpleName == moduleName;
14 | Check(b);
15 | }
16 | }
17 |
18 | public static void Check(RuntimeDefinitions.PEFile* file) {
19 | Check(Memory.TryReadUIntPtr((nuint)file, out _));
20 | }
21 |
22 | public static void Check(RuntimeDefinitions.PEImage* image, bool inMemory) {
23 | Check(Memory.TryReadUIntPtr((nuint)image, out _));
24 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx40) {
25 | var m_Path = ((RuntimeDefinitions.PEImage_40*)image)->m_path;
26 | bool b = Memory.TryReadUnicodeString(m_Path.m_buffer, out var path) && (inMemory ? path.Length == 0 : File.Exists(path));
27 | Check(b);
28 | }
29 | else {
30 | var m_Path = ((RuntimeDefinitions.PEImage_20*)image)->m_path;
31 | bool b = Memory.TryReadUnicodeString(m_Path.m_buffer, out var path) && (inMemory ? path.Length == 0 : File.Exists(path));
32 | Check(b);
33 | }
34 | }
35 |
36 | public static void Check(RuntimeDefinitions.PEImageLayout* layout, bool inMemory) {
37 | Check(Memory.TryReadUIntPtr((nuint)layout, out _));
38 | Check((ushort)layout->__base.m_base == 0);
39 | Check(layout->__base.m_pCorHeader - layout->__base.m_base == (inMemory ? 0x208u : 0x2008));
40 | }
41 |
42 | public static void Check(RuntimeDefinitions.IMAGE_COR20_HEADER* corHeader) {
43 | Check(Memory.TryReadUIntPtr((nuint)corHeader, out _));
44 | Check(corHeader->cb == 0x48);
45 | Check(corHeader->MajorRuntimeVersion == 2);
46 | Check(corHeader->MinorRuntimeVersion == 5);
47 | }
48 |
49 | public static void Check(RuntimeDefinitions.STORAGESIGNATURE* storageSignature) {
50 | Check(Memory.TryReadUIntPtr((nuint)storageSignature, out _));
51 | Check(storageSignature->lSignature == RuntimeDefinitions.STORAGE_MAGIC_SIG);
52 | Check(storageSignature->iMajorVer == 1);
53 | Check(storageSignature->iMinorVer == 1);
54 | }
55 |
56 | public static void Check([DoesNotReturnIf(false)] bool condition) {
57 | if (!condition) {
58 | Debug2.Assert(false, "Contains error in RuntimeDefinitions");
59 | throw new InvalidOperationException("Contains error in RuntimeDefinitions");
60 | }
61 | }
62 |
63 | public static bool Verify(Pointer template, bool? testUncompressed, Predicate checker) {
64 | for (int i = 0; i < 5; i++) {
65 | for (TestAssemblyFlags inMemory = 0; inMemory <= TestAssemblyFlags.InMemory; inMemory += (int)TestAssemblyFlags.InMemory) {
66 | var uncompressed = testUncompressed == true ? TestAssemblyFlags.Uncompressed : 0;
67 | var uncompressedEnd = testUncompressed == false ? 0 : TestAssemblyFlags.Uncompressed;
68 | for (; uncompressed <= uncompressedEnd; uncompressed += (int)TestAssemblyFlags.Uncompressed) {
69 | var assembly = TestAssemblyManager.GetAssembly((TestAssemblyFlags)i | inMemory | uncompressed);
70 | nuint value = ReadUIntPtr(template, assembly.ModuleHandle);
71 | if (value == 0 || !checker(value))
72 | return false;
73 | }
74 | }
75 | }
76 | return true;
77 | }
78 |
79 | public static uint ReadUInt32(Pointer template, nuint baseAddress) {
80 | nuint address = ReadPointer(template, baseAddress);
81 | if (!Memory.TryReadUInt32(address, out uint value))
82 | return default;
83 | return value;
84 | }
85 |
86 | public static nuint ReadUIntPtr(Pointer template, nuint baseAddress) {
87 | nuint address = ReadPointer(template, baseAddress);
88 | if (!Memory.TryReadUIntPtr(address, out nuint value))
89 | return default;
90 | return value;
91 | }
92 |
93 | public static string ReadUnicodeString(Pointer template, nuint baseAddress) {
94 | nuint address = ReadPointer(template, baseAddress);
95 | if (!Memory.TryReadUnicodeString(address, out var value))
96 | return string.Empty;
97 | return value;
98 | }
99 |
100 | public static nuint ReadPointer(Pointer template, nuint baseAddress) {
101 | if (template.IsEmpty)
102 | return default;
103 | var pointer = MakePointer(template, baseAddress);
104 | if (!Memory.TryToAddress(pointer, out nuint address))
105 | return default;
106 | return address;
107 | }
108 |
109 | public static Pointer MakePointer(Pointer template, nuint baseAddress) {
110 | Debug2.Assert(!template.IsEmpty);
111 | return new Pointer(template) { BaseAddress = baseAddress };
112 | }
113 |
114 | public static Pointer[] WithOffset(Pointer basePointer, uint[] offsets) {
115 | var pointers = new Pointer[offsets.Length];
116 | for (int i = 0; i < pointers.Length; i++) {
117 | var pointer = new Pointer(basePointer);
118 | pointer.Add(offsets[i]);
119 | pointers[i] = pointer;
120 | }
121 | return pointers;
122 | }
123 |
124 | public static Pointer WithOffset(Pointer basePointer, uint offset) {
125 | var pointer = new Pointer(basePointer);
126 | pointer.Add(offset);
127 | return pointer;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/MetadataLocator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32112.339
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator", "MetadataLocator\MetadataLocator.csproj", "{AED69D77-8C7B-41BA-9967-AC9B522430F9}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test", "MetadataLocator.Test\MetadataLocator.Test.csproj", "{BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CLR20.x86", "MetadataLocator.Test.CLR20.x86\MetadataLocator.Test.CLR20.x86.csproj", "{7368F4FE-ED71-433B-A525-F20EA7FA488D}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CLR20.x64", "MetadataLocator.Test.CLR20.x64\MetadataLocator.Test.CLR20.x64.csproj", "{B925862F-E6BB-4F18-B560-B19153AB97D7}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CLR40.x86", "MetadataLocator.Test.CLR40.x86\MetadataLocator.Test.CLR40.x86.csproj", "{7BA7644F-B706-415E-AE0E-CC5FC6247021}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CLR40.x64", "MetadataLocator.Test.CLR40.x64\MetadataLocator.Test.CLR40.x64.csproj", "{195944D9-ABA2-44CE-9F7D-AE432D114BD1}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CoreCLR31.x86", "MetadataLocator.Test.CoreCLR31.x86\MetadataLocator.Test.CoreCLR31.x86.csproj", "{104612D5-3CBB-4235-914C-88C5B4736D16}"
19 | EndProject
20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CoreCLR31.x64", "MetadataLocator.Test.CoreCLR31.x64\MetadataLocator.Test.CoreCLR31.x64.csproj", "{EE92CDA2-49A8-473B-AC22-81F5605B9B80}"
21 | EndProject
22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CoreCLR60.x86", "MetadataLocator.Test.CoreCLR60.x86\MetadataLocator.Test.CoreCLR60.x86.csproj", "{BCC1A39F-DB7B-4949-97C6-195E2E4B2526}"
23 | EndProject
24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CoreCLR60.x64", "MetadataLocator.Test.CoreCLR60.x64\MetadataLocator.Test.CoreCLR60.x64.csproj", "{93F97993-AF94-4E41-959E-E3154DC8B179}"
25 | EndProject
26 | Global
27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
28 | Debug|Any CPU = Debug|Any CPU
29 | Release|Any CPU = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
32 | {AED69D77-8C7B-41BA-9967-AC9B522430F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {AED69D77-8C7B-41BA-9967-AC9B522430F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {AED69D77-8C7B-41BA-9967-AC9B522430F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {AED69D77-8C7B-41BA-9967-AC9B522430F9}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {7368F4FE-ED71-433B-A525-F20EA7FA488D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {7368F4FE-ED71-433B-A525-F20EA7FA488D}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {7368F4FE-ED71-433B-A525-F20EA7FA488D}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {7368F4FE-ED71-433B-A525-F20EA7FA488D}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {B925862F-E6BB-4F18-B560-B19153AB97D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {B925862F-E6BB-4F18-B560-B19153AB97D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {B925862F-E6BB-4F18-B560-B19153AB97D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {B925862F-E6BB-4F18-B560-B19153AB97D7}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {7BA7644F-B706-415E-AE0E-CC5FC6247021}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {7BA7644F-B706-415E-AE0E-CC5FC6247021}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {7BA7644F-B706-415E-AE0E-CC5FC6247021}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {7BA7644F-B706-415E-AE0E-CC5FC6247021}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {195944D9-ABA2-44CE-9F7D-AE432D114BD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {195944D9-ABA2-44CE-9F7D-AE432D114BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {195944D9-ABA2-44CE-9F7D-AE432D114BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {195944D9-ABA2-44CE-9F7D-AE432D114BD1}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {104612D5-3CBB-4235-914C-88C5B4736D16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {104612D5-3CBB-4235-914C-88C5B4736D16}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {104612D5-3CBB-4235-914C-88C5B4736D16}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {104612D5-3CBB-4235-914C-88C5B4736D16}.Release|Any CPU.Build.0 = Release|Any CPU
60 | {EE92CDA2-49A8-473B-AC22-81F5605B9B80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61 | {EE92CDA2-49A8-473B-AC22-81F5605B9B80}.Debug|Any CPU.Build.0 = Debug|Any CPU
62 | {EE92CDA2-49A8-473B-AC22-81F5605B9B80}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {EE92CDA2-49A8-473B-AC22-81F5605B9B80}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {BCC1A39F-DB7B-4949-97C6-195E2E4B2526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65 | {BCC1A39F-DB7B-4949-97C6-195E2E4B2526}.Debug|Any CPU.Build.0 = Debug|Any CPU
66 | {BCC1A39F-DB7B-4949-97C6-195E2E4B2526}.Release|Any CPU.ActiveCfg = Release|Any CPU
67 | {BCC1A39F-DB7B-4949-97C6-195E2E4B2526}.Release|Any CPU.Build.0 = Release|Any CPU
68 | {93F97993-AF94-4E41-959E-E3154DC8B179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 | {93F97993-AF94-4E41-959E-E3154DC8B179}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 | {93F97993-AF94-4E41-959E-E3154DC8B179}.Release|Any CPU.ActiveCfg = Release|Any CPU
71 | {93F97993-AF94-4E41-959E-E3154DC8B179}.Release|Any CPU.Build.0 = Release|Any CPU
72 | EndGlobalSection
73 | GlobalSection(SolutionProperties) = preSolution
74 | HideSolutionNode = FALSE
75 | EndGlobalSection
76 | GlobalSection(ExtensibilityGlobals) = postSolution
77 | SolutionGuid = {638DD4A8-E582-4EF9-B751-DF759C0C80FC}
78 | EndGlobalSection
79 | EndGlobal
80 |
--------------------------------------------------------------------------------
/MetadataLocator.Test/TestDriver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 |
5 | namespace MetadataLocator.Test;
6 |
7 | public static unsafe class TestDriver {
8 | static int indent;
9 |
10 | public static void Test() {
11 | RuntimeDefinitionTests.VerifySize();
12 | MetadataImportTests.Test();
13 | Print(Assembly.GetEntryAssembly().ManifestModule, true);
14 | Print(typeof(MetadataInfo).Module, true);
15 | //RunTestAssemblys(5);
16 | Console.ReadKey(true);
17 | }
18 |
19 | static void RunTestAssemblys(int count) {
20 | for (int i = 0; i < count; i++) {
21 | for (TestAssemblyFlags inMemory = 0; inMemory <= TestAssemblyFlags.InMemory; inMemory += (int)TestAssemblyFlags.InMemory) {
22 | for (TestAssemblyFlags uncompressed = 0; uncompressed <= TestAssemblyFlags.Uncompressed; uncompressed += (int)TestAssemblyFlags.Uncompressed) {
23 | var assembly = TestAssemblyManager.GetAssembly((TestAssemblyFlags)i | inMemory | uncompressed);
24 | Print(assembly.Module, true);
25 | }
26 | }
27 | }
28 | }
29 |
30 | static void Print(Module module, bool end = false) {
31 | Print($"{module.Assembly.GetName().Name}: {{");
32 | indent++;
33 | Print(nameof(PEInfo), PEInfo.Create(module));
34 | Print(nameof(MetadataInfo), MetadataInfo.Create(module), true);
35 | indent--;
36 | Print(end ? "}" : "},");
37 | }
38 |
39 | static void Print(string name, PEInfo peInfo, bool end = false) {
40 | if (peInfo.IsInvalid) {
41 | Print(end ? $"{name}: null" : $"{name}: null,");
42 | return;
43 | }
44 |
45 | Print($"{name}: {{");
46 | indent++;
47 | Print($"{nameof(PEInfo.FilePath)}: {peInfo.FilePath},");
48 | Print($"{nameof(PEInfo.InMemory)}: {peInfo.InMemory},");
49 | Print(nameof(PEInfo.FlatLayout), peInfo.FlatLayout);
50 | Print(nameof(PEInfo.MappedLayout), peInfo.MappedLayout);
51 | Print(nameof(PEInfo.LoadedLayout), peInfo.LoadedLayout, true);
52 | indent--;
53 | Print(end ? "}" : "},");
54 | }
55 |
56 | static void Print(string name, PEImageLayout imageLayout, bool end = false) {
57 | if (imageLayout.IsInvalid) {
58 | Print(end ? $"{name}: null" : $"{name}: null,");
59 | return;
60 | }
61 |
62 | Print($"{name}: {{");
63 | indent++;
64 | Print($"{nameof(PEImageLayout.ImageBase)}: {FormatHex(imageLayout.ImageBase)},");
65 | Print($"{nameof(PEImageLayout.ImageSize)}: {FormatHex(imageLayout.ImageSize)},");
66 | Print($"{nameof(PEImageLayout.CorHeaderAddress)}: {FormatHex(imageLayout.CorHeaderAddress)}");
67 | indent--;
68 | Print(end ? "}" : "},");
69 | }
70 |
71 | static void Print(string name, MetadataInfo metadataInfo, bool end = false) {
72 | if (metadataInfo.IsInvalid) {
73 | Print(end ? $"{name}: null" : $"{name}: null,");
74 | return;
75 | }
76 |
77 | Print($"{name}: {{");
78 | indent++;
79 | Print($"{nameof(MetadataInfo.MetadataAddress)}: {FormatHex(metadataInfo.MetadataAddress)},");
80 | Print($"{nameof(MetadataInfo.MetadataSize)}: {FormatHex(metadataInfo.MetadataSize)},");
81 | Print(nameof(MetadataInfo.Schema), metadataInfo.Schema);
82 | Print(nameof(MetadataInfo.TableStream), metadataInfo.TableStream);
83 | Print(nameof(MetadataInfo.StringHeap), metadataInfo.StringHeap);
84 | Print(nameof(MetadataInfo.UserStringHeap), metadataInfo.UserStringHeap);
85 | Print(nameof(MetadataInfo.GuidHeap), metadataInfo.GuidHeap);
86 | Print(nameof(MetadataInfo.BlobHeap), metadataInfo.BlobHeap, true);
87 | indent--;
88 | Print(end ? "}" : "},");
89 | }
90 |
91 | static void Print(string name, MetadataSchema schema, bool end = false) {
92 | if (schema.IsInvalid) {
93 | Print(end ? $"{name}: null" : $"{name}: null,");
94 | return;
95 | }
96 |
97 | Print($"{name}: {{");
98 | indent++;
99 | Print($"{nameof(MetadataSchema.Reserved1)}: {schema.Reserved1},");
100 | Print($"{nameof(MetadataSchema.MajorVersion)}: {schema.MajorVersion},");
101 | Print($"{nameof(MetadataSchema.MinorVersion)}: {schema.MinorVersion},");
102 | Print($"{nameof(MetadataSchema.Flags)}: {schema.Flags},");
103 | Print($"{nameof(MetadataSchema.Log2Rid)}: {schema.Log2Rid},");
104 | Print($"{nameof(MetadataSchema.ValidMask)}: {FormatHex(schema.ValidMask)},");
105 | Print($"{nameof(MetadataSchema.SortedMask)}: {FormatHex(schema.SortedMask)},");
106 | Print($"{nameof(MetadataSchema.RowCounts)}: \"{string.Join(",", schema.RowCounts.Select(t => $"0x{t:X}").ToArray())}\",");
107 | Print($"{nameof(MetadataSchema.ExtraData)}: {schema.ExtraData}");
108 | indent--;
109 | Print(end ? "}" : "},");
110 | }
111 |
112 | static void Print(string name, MetadataTableInfo tableInfo, bool end = false) {
113 | if (tableInfo.IsInvalid) {
114 | Print(end ? $"{name}: null" : $"{name}: null,");
115 | return;
116 | }
117 |
118 | Print($"{name}: {{");
119 | indent++;
120 | Print($"{nameof(MetadataTableInfo.Address)}: {FormatHex(tableInfo.Address)},");
121 | Print($"{nameof(MetadataTableInfo.Length)}: {FormatHex(tableInfo.Length)},");
122 | Print($"{nameof(MetadataTableInfo.IsCompressed)}: {tableInfo.IsCompressed}");
123 | Print($"{nameof(MetadataTableInfo.TableCount)}: {tableInfo.TableCount},");
124 | Print($"{nameof(MetadataTableInfo.RowSizes)}: \"{string.Join(",", tableInfo.RowSizes.Select(t => $"0x{t:X}").ToArray())}\"");
125 | indent--;
126 | Print(end ? "}" : "},");
127 | }
128 |
129 | static void Print(string name, MetadataHeapInfo heapInfo, bool end = false) {
130 | if (heapInfo.IsInvalid) {
131 | Print($"{name}: null,");
132 | return;
133 | }
134 |
135 | Print($"{name}: {{");
136 | indent++;
137 | Print($"{nameof(MetadataStreamInfo.Address)}: {FormatHex(heapInfo.Address)},");
138 | Print($"{nameof(MetadataStreamInfo.Length)}: {FormatHex(heapInfo.Length)}");
139 | indent--;
140 | Print(end ? "}" : "},");
141 | }
142 |
143 | static void Print(string value) {
144 | Console.WriteLine(new string(' ', indent * 2) + value);
145 | }
146 |
147 | static string FormatHex(uint value) {
148 | return $"0x{value:X8}";
149 | }
150 |
151 | static string FormatHex(ulong value) {
152 | return $"0x{value:X16}";
153 | }
154 |
155 | static string FormatHex(nuint value) {
156 | return sizeof(nuint) == 4 ? FormatHex((uint)value) : FormatHex((ulong)value);
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
364 |
365 | # wwh1004
366 | launchSettings.json
--------------------------------------------------------------------------------
/MetadataLocator/PEInfoImpl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using static MetadataLocator.RuntimeDefinitions;
5 | using SR = System.Reflection;
6 |
7 | namespace MetadataLocator;
8 |
9 | static unsafe class PEInfoImpl {
10 | static Pointer filePathPointer = Pointer.Empty;
11 | static Pointer flatImageLayoutPointer = Pointer.Empty;
12 | static Pointer mappedImageLayoutPointer = Pointer.Empty;
13 | static Pointer loadedImageLayoutPointer = Pointer.Empty;
14 | static bool isInitialized;
15 |
16 | static void Initialize() {
17 | if (isInitialized)
18 | return;
19 |
20 | filePathPointer = ScanFilePathPointer();
21 | Debug2.Assert(!filePathPointer.IsEmpty);
22 | loadedImageLayoutPointer = ScanLoadedImageLayoutPointer(out bool isMappedLayoutExisting);
23 | Debug2.Assert(!loadedImageLayoutPointer.IsEmpty);
24 | if (isMappedLayoutExisting) {
25 | var t = new Pointer(loadedImageLayoutPointer);
26 | t.Offsets[t.Offsets.Count - 1] -= (uint)sizeof(nuint);
27 | mappedImageLayoutPointer = t;
28 | t = new Pointer(t);
29 | t.Offsets[t.Offsets.Count - 1] -= (uint)sizeof(nuint);
30 | flatImageLayoutPointer = t;
31 | }
32 | else {
33 | var t = new Pointer(loadedImageLayoutPointer);
34 | t.Offsets[t.Offsets.Count - 1] -= (uint)sizeof(nuint);
35 | flatImageLayoutPointer = t;
36 | }
37 | isInitialized = true;
38 | }
39 |
40 | static Pointer ScanFilePathPointer() {
41 | const bool InMemory = false;
42 |
43 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
44 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
45 | nuint module = assembly.ModuleHandle;
46 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
47 | // Get native Module object
48 |
49 | uint m_file_Offset;
50 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453)
51 | m_file_Offset = (uint)((nuint)(&Module_453.Dummy->m_file) - (nuint)Module_453.Dummy);
52 | else
53 | m_file_Offset = (uint)((nuint)(&Module_20.Dummy->m_file) - (nuint)Module_20.Dummy);
54 | nuint m_file = *(nuint*)(module + m_file_Offset);
55 | Utils.Check((PEFile*)m_file);
56 | // Module.m_file
57 |
58 | uint m_openedILimage_Offset = (uint)((nuint)(&PEFile.Dummy->m_openedILimage) - (nuint)PEFile.Dummy);
59 | nuint m_openedILimage = *(nuint*)(m_file + m_openedILimage_Offset);
60 | Utils.Check((PEImage*)m_openedILimage, InMemory);
61 | // PEFile.m_openedILimage
62 |
63 | uint m_path_m_buffer_Offset = 0;
64 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx40)
65 | m_path_m_buffer_Offset = (uint)((nuint)(&PEImage_40.Dummy->m_path.m_buffer) - (nuint)PEImage_40.Dummy);
66 | else
67 | m_path_m_buffer_Offset = (uint)((nuint)(&PEImage_20.Dummy->m_path.m_buffer) - (nuint)PEImage_20.Dummy);
68 | nuint m_path_m_buffer = *(nuint*)(m_openedILimage + m_path_m_buffer_Offset);
69 | Utils.Check(Memory.TryReadUnicodeString(m_path_m_buffer, out var path) && File.Exists(path));
70 | // PEImage.m_path.m_buffer
71 |
72 | var pointer = new Pointer(new[] {
73 | m_file_Offset,
74 | m_openedILimage_Offset,
75 | m_path_m_buffer_Offset
76 | });
77 | Utils.Check(Utils.Verify(pointer, null, p => Memory.TryReadUnicodeString(p, out var path) && (path.Length == 0 || File.Exists(path))));
78 | pointer.Add(0);
79 | return pointer;
80 | }
81 |
82 | static Pointer ScanLoadedImageLayoutPointer(out bool isMappedLayoutExisting) {
83 | const bool InMemory = true;
84 |
85 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
86 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
87 | nuint module = assembly.ModuleHandle;
88 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
89 | // Get native Module object
90 |
91 | uint m_file_Offset;
92 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453)
93 | m_file_Offset = (uint)((nuint)(&Module_453.Dummy->m_file) - (nuint)Module_453.Dummy);
94 | else
95 | m_file_Offset = (uint)((nuint)(&Module_20.Dummy->m_file) - (nuint)Module_20.Dummy);
96 | nuint m_file = *(nuint*)(module + m_file_Offset);
97 | Utils.Check((PEFile*)m_file);
98 | // Module.m_file
99 |
100 | uint m_openedILimage_Offset = (uint)((nuint)(&PEFile.Dummy->m_openedILimage) - (nuint)PEFile.Dummy);
101 | nuint m_openedILimage = *(nuint*)(m_file + m_openedILimage_Offset);
102 | Utils.Check((PEImage*)m_openedILimage, InMemory);
103 | // PEFile.m_openedILimage
104 |
105 | nuint m_pMDImport = MetadataImport.Create(assembly.Module).This;
106 | uint m_pMDImport_Offset;
107 | bool found = false;
108 | for (m_pMDImport_Offset = 0x40; m_pMDImport_Offset < 0xD0; m_pMDImport_Offset += 4) {
109 | if (*(nuint*)(m_openedILimage + m_pMDImport_Offset) != m_pMDImport)
110 | continue;
111 | found = true;
112 | break;
113 | }
114 | Utils.Check(found);
115 | // PEFile.m_pMDImport (not use, just for locating previous member 'm_pLayouts')
116 | isMappedLayoutExisting = false;
117 | uint m_pLayouts_Loaded_Offset = m_pMDImport_Offset - 4 - (uint)sizeof(nuint);
118 | uint m_pLayouts_Offset_Min = m_pLayouts_Loaded_Offset - (4 * (uint)sizeof(nuint));
119 | nuint actualModuleBase = ReflectionHelpers.GetNativeModuleHandle(assembly.Module);
120 | found = false;
121 | for (; m_pLayouts_Loaded_Offset >= m_pLayouts_Offset_Min; m_pLayouts_Loaded_Offset -= 4) {
122 | var m_pLayout = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset);
123 | if (!Memory.TryReadUIntPtr((nuint)m_pLayout, out _))
124 | continue;
125 | if (!Memory.TryReadUIntPtr(m_pLayout->__vfptr, out _))
126 | continue;
127 | if (actualModuleBase != m_pLayout->__base.m_base)
128 | continue;
129 | Debug2.Assert(InMemory);
130 | var m_pLayout_prev1 = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset - (uint)sizeof(nuint));
131 | var m_pLayout_prev2 = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset - (2 * (uint)sizeof(nuint)));
132 | if (m_pLayout_prev2 == m_pLayout)
133 | isMappedLayoutExisting = true;
134 | else if (m_pLayout_prev1 == m_pLayout)
135 | isMappedLayoutExisting = false; // latest .NET, TODO: update comment when .NET 7.0 released
136 | found = true;
137 | break;
138 | }
139 | Utils.Check(found);
140 | nuint m_pLayouts_Loaded = *(nuint*)(m_openedILimage + m_pLayouts_Loaded_Offset);
141 | Utils.Check((RuntimeDefinitions.PEImageLayout*)m_pLayouts_Loaded, InMemory);
142 | // PEImage.m_pLayouts[IMAGE_LOADED]
143 |
144 | uint m_pCorHeader_Offset = (uint)((nuint)(&RuntimeDefinitions.PEImageLayout.Dummy->__base.m_pCorHeader) - (nuint)RuntimeDefinitions.PEImageLayout.Dummy);
145 | nuint m_pCorHeader = *(nuint*)(m_pLayouts_Loaded + m_pCorHeader_Offset);
146 | Utils.Check((IMAGE_COR20_HEADER*)m_pCorHeader);
147 | // PEImageLayout.m_pCorHeader
148 |
149 | var pointer = new Pointer(new[] {
150 | m_file_Offset,
151 | m_openedILimage_Offset,
152 | m_pLayouts_Loaded_Offset
153 | });
154 | Utils.Check(Utils.Verify(pointer, null, p => Memory.TryReadUIntPtr(p + (uint)sizeof(nuint), out nuint @base) && (ushort)@base == 0));
155 | Utils.Check(Utils.Verify(Utils.WithOffset(pointer, m_pCorHeader_Offset), null, p => Memory.TryReadUInt32(p, out uint cb) && cb == 0x48));
156 | return pointer;
157 | }
158 |
159 | public static PEInfo GetPEInfo(SR.Module module) {
160 | if (module is null)
161 | throw new ArgumentNullException(nameof(module));
162 |
163 | Initialize();
164 | nuint moduleHandle = ReflectionHelpers.GetModuleHandle(module);
165 | var peInfo = new PEInfo {
166 | FilePath = Utils.ReadUnicodeString(filePathPointer, moduleHandle),
167 | FlatLayout = GetImageLayout(Utils.ReadUIntPtr(flatImageLayoutPointer, moduleHandle)),
168 | MappedLayout = GetImageLayout(Utils.ReadUIntPtr(mappedImageLayoutPointer, moduleHandle)),
169 | LoadedLayout = GetImageLayout(Utils.ReadUIntPtr(loadedImageLayoutPointer, moduleHandle))
170 | };
171 | return peInfo;
172 | }
173 |
174 | static PEImageLayout GetImageLayout(nuint pImageLayout) {
175 | if (pImageLayout == 0)
176 | return new();
177 |
178 | var pLayout = (RuntimeDefinitions.PEImageLayout*)pImageLayout;
179 | var layout = new PEImageLayout {
180 | ImageBase = pLayout->__base.m_base,
181 | ImageSize = pLayout->__base.m_size,
182 | CorHeaderAddress = pLayout->__base.m_pCorHeader
183 | };
184 | return layout;
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = crlf
7 | indent_size = 4
8 | indent_style = tab
9 | insert_final_newline = true
10 | tab_width = 4
11 | trim_trailing_whitespace = true
12 |
13 | # C# files
14 | [*.cs]
15 |
16 | #### .NET Coding Conventions ####
17 |
18 | # Organize usings
19 | dotnet_separate_import_directive_groups = false
20 | dotnet_sort_system_directives_first = true
21 |
22 | # this. and Me. preferences
23 | dotnet_style_qualification_for_event = false:suggestion
24 | dotnet_style_qualification_for_field = false:suggestion
25 | dotnet_style_qualification_for_method = false:suggestion
26 | dotnet_style_qualification_for_property = false:suggestion
27 |
28 | # Language keywords vs BCL types preferences
29 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
30 | dotnet_style_predefined_type_for_member_access = true:suggestion
31 |
32 | # Parentheses preferences
33 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
34 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
35 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
36 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
37 |
38 | # Modifier preferences
39 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
40 |
41 | # Expression-level preferences
42 | dotnet_style_coalesce_expression = true:suggestion
43 | dotnet_style_collection_initializer = true:suggestion
44 | dotnet_style_explicit_tuple_names = true:suggestion
45 | dotnet_style_namespace_match_folder = true:suggestion
46 | dotnet_style_null_propagation = true:suggestion
47 | dotnet_style_object_initializer = true:suggestion
48 | dotnet_style_operator_placement_when_wrapping = end_of_line
49 | dotnet_style_prefer_auto_properties = false:suggestion
50 | dotnet_style_prefer_compound_assignment = true:suggestion
51 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
52 | dotnet_style_prefer_conditional_expression_over_return = true:silent
53 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
54 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
55 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
56 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
57 | dotnet_style_prefer_simplified_interpolation = true:suggestion
58 |
59 | # Field preferences
60 | dotnet_style_readonly_field = true:suggestion
61 |
62 | # Parameter preferences
63 | dotnet_code_quality_unused_parameters = all:suggestion
64 |
65 | # Suppression preferences
66 | dotnet_remove_unnecessary_suppression_exclusions = none
67 |
68 | # New line preferences
69 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent
70 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
71 |
72 | #### C# Coding Conventions ####
73 |
74 | # var preferences
75 | csharp_style_var_elsewhere = true:suggestion
76 | csharp_style_var_for_built_in_types = false:silent
77 | csharp_style_var_when_type_is_apparent = true:suggestion
78 |
79 | # Expression-bodied members
80 | csharp_style_expression_bodied_accessors = true:suggestion
81 | csharp_style_expression_bodied_constructors = false:suggestion
82 | csharp_style_expression_bodied_indexers = true:suggestion
83 | csharp_style_expression_bodied_lambdas = true:suggestion
84 | csharp_style_expression_bodied_local_functions = false:suggestion
85 | csharp_style_expression_bodied_methods = false:suggestion
86 | csharp_style_expression_bodied_operators = false:suggestion
87 | csharp_style_expression_bodied_properties = true:suggestion
88 |
89 | # Pattern matching preferences
90 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
91 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
92 | csharp_style_prefer_not_pattern = true:suggestion
93 | csharp_style_prefer_pattern_matching = true:silent
94 | csharp_style_prefer_switch_expression = false:suggestion
95 |
96 | # Null-checking preferences
97 | csharp_style_conditional_delegate_call = true:suggestion
98 |
99 | # Modifier preferences
100 | csharp_prefer_static_local_function = true:suggestion
101 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
102 |
103 | # Code-block preferences
104 | csharp_prefer_braces = false:silent
105 | csharp_prefer_simple_using_statement = true:suggestion
106 | csharp_style_namespace_declarations = file_scoped:suggestion
107 |
108 | # Expression-level preferences
109 | csharp_prefer_simple_default_expression = true:suggestion
110 | csharp_style_deconstructed_variable_declaration = true:suggestion
111 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
112 | csharp_style_inlined_variable_declaration = true:suggestion
113 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
114 | csharp_style_prefer_index_operator = true:suggestion
115 | csharp_style_prefer_null_check_over_type_check = true:suggestion
116 | csharp_style_prefer_range_operator = true:suggestion
117 | csharp_style_throw_expression = true:suggestion
118 | csharp_style_unused_value_assignment_preference = unused_local_variable:silent
119 | csharp_style_unused_value_expression_statement_preference = unused_local_variable:silent
120 |
121 | # 'using' directive preferences
122 | csharp_using_directive_placement = outside_namespace:suggestion
123 |
124 | # New line preferences
125 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
126 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
127 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
128 |
129 | #### C# Formatting Rules ####
130 |
131 | # New line preferences
132 | csharp_new_line_before_catch = true
133 | csharp_new_line_before_else = true
134 | csharp_new_line_before_finally = true
135 | csharp_new_line_before_members_in_anonymous_types = true
136 | csharp_new_line_before_members_in_object_initializers = true
137 | csharp_new_line_before_open_brace = none
138 | csharp_new_line_between_query_expression_clauses = false
139 |
140 | # Indentation preferences
141 | csharp_indent_block_contents = true
142 | csharp_indent_braces = false
143 | csharp_indent_case_contents = true
144 | csharp_indent_case_contents_when_block = false
145 | csharp_indent_labels = one_less_than_current
146 | csharp_indent_switch_labels = false
147 |
148 | # Space preferences
149 | csharp_space_after_cast = false
150 | csharp_space_after_colon_in_inheritance_clause = true
151 | csharp_space_after_comma = true
152 | csharp_space_after_dot = false
153 | csharp_space_after_keywords_in_control_flow_statements = true
154 | csharp_space_after_semicolon_in_for_statement = true
155 | csharp_space_around_binary_operators = before_and_after
156 | csharp_space_around_declaration_statements = false
157 | csharp_space_before_colon_in_inheritance_clause = true
158 | csharp_space_before_comma = false
159 | csharp_space_before_dot = false
160 | csharp_space_before_open_square_brackets = false
161 | csharp_space_before_semicolon_in_for_statement = false
162 | csharp_space_between_empty_square_brackets = false
163 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
164 | csharp_space_between_method_call_name_and_opening_parenthesis = false
165 | csharp_space_between_method_call_parameter_list_parentheses = false
166 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
167 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
168 | csharp_space_between_method_declaration_parameter_list_parentheses = false
169 | csharp_space_between_parentheses = false
170 | csharp_space_between_square_brackets = false
171 |
172 | # Wrapping preferences
173 | csharp_preserve_single_line_blocks = true
174 | csharp_preserve_single_line_statements = true
175 |
176 | #### Naming styles ####
177 |
178 | # Naming rules
179 |
180 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
181 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
182 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
183 |
184 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
185 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
186 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
187 |
188 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
189 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
190 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
191 |
192 | # Symbol specifications
193 |
194 | dotnet_naming_symbols.interface.applicable_kinds = interface
195 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
196 | dotnet_naming_symbols.interface.required_modifiers =
197 |
198 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
199 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
200 | dotnet_naming_symbols.types.required_modifiers =
201 |
202 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
203 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
204 | dotnet_naming_symbols.non_field_members.required_modifiers =
205 |
206 | # Naming styles
207 |
208 | dotnet_naming_style.pascal_case.required_prefix =
209 | dotnet_naming_style.pascal_case.required_suffix =
210 | dotnet_naming_style.pascal_case.word_separator =
211 | dotnet_naming_style.pascal_case.capitalization = pascal_case
212 |
213 | dotnet_naming_style.begins_with_i.required_prefix = I
214 | dotnet_naming_style.begins_with_i.required_suffix =
215 | dotnet_naming_style.begins_with_i.word_separator =
216 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
217 |
--------------------------------------------------------------------------------
/MetadataLocator/TestAssemblyManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Reflection;
6 |
7 | namespace MetadataLocator;
8 |
9 | ///
10 | /// Test assembly generation options
11 | ///
12 | [Flags]
13 | enum TestAssemblyFlags {
14 | ///
15 | /// The number of assembly
16 | ///
17 | IndexMask = 0xFF,
18 |
19 | ///
20 | /// Load it from memory
21 | ///
22 | InMemory = 0x100,
23 |
24 | ///
25 | /// Use #- table stream
26 | ///
27 | Uncompressed = 0x200
28 | }
29 |
30 | ///
31 | /// Assembly for test
32 | ///
33 | sealed class TestAssembly {
34 | public TestAssemblyFlags Flags { get; }
35 |
36 | public int Index => (int)(Flags & TestAssemblyFlags.IndexMask);
37 |
38 | public bool InMemory => (Flags & TestAssemblyFlags.InMemory) != 0;
39 |
40 | public bool Uncompressed => (Flags & TestAssemblyFlags.Uncompressed) != 0;
41 |
42 | public Assembly Assembly { get; }
43 |
44 | public Module Module { get; }
45 |
46 | public nuint ModuleHandle { get; }
47 |
48 | public TestAssembly(TestAssemblyFlags flags, Assembly assembly) {
49 | Flags = flags;
50 | Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
51 | Module = assembly.ManifestModule;
52 | ModuleHandle = ReflectionHelpers.GetModuleHandle(assembly.ManifestModule);
53 | }
54 | }
55 |
56 | ///
57 | /// Manager of
58 | ///
59 | sealed class TestAssemblyManager {
60 | const int CACHE_MAGIC = 2;
61 | // Update it if old cache format is invalid
62 |
63 | static readonly Dictionary assemblies = new();
64 |
65 | ///
66 | /// Get a test assembly
67 | ///
68 | ///
69 | ///
70 | public static TestAssembly GetAssembly(TestAssemblyFlags flags) {
71 | if (!assemblies.TryGetValue(flags, out var testAssembly)) {
72 | string path = GetOrCreateAssembly(flags);
73 | bool inMemory = (flags & TestAssemblyFlags.InMemory) != 0;
74 | var assembly = inMemory ? Assembly.Load(File.ReadAllBytes(path)) : Assembly.LoadFile(path);
75 | testAssembly = new TestAssembly(flags, assembly);
76 | assemblies.Add(flags, testAssembly);
77 | }
78 | return testAssembly;
79 | }
80 |
81 | static string GetOrCreateAssembly(TestAssemblyFlags flags) {
82 | var tempDirectory = Path.Combine(Path.GetTempPath(), $"MetadataLocator_{CACHE_MAGIC:X2}_{RuntimeEnvironment.Version}");
83 | if (!Directory.Exists(tempDirectory))
84 | Directory.CreateDirectory(tempDirectory);
85 | string cachePath = Path.Combine(tempDirectory, $"{(uint)flags:X8}.dll");
86 |
87 | if (File.Exists(cachePath))
88 | return cachePath;
89 |
90 | var rawAssembly = Decompress(data);
91 |
92 | ulong a = 0x5F5F5F5F5F5F5F5F; // ________
93 | ulong b = FlagsToAscii(flags); // 00000000
94 | int c = ReplaceAll(rawAssembly, a, b);
95 | Debug2.Assert(c == 2);
96 | if ((flags & TestAssemblyFlags.Uncompressed) != 0)
97 | ConvertToUncompressed(rawAssembly);
98 | File.WriteAllBytes(cachePath, rawAssembly);
99 | return cachePath;
100 | }
101 |
102 | static unsafe ulong FlagsToAscii(TestAssemblyFlags flags) {
103 | var s = $"{(uint)flags:X8}";
104 | ulong n = 0;
105 | for (int i = 0; i < 8; i++)
106 | n |= (ulong)s[i] << (i * 8);
107 | return n;
108 | }
109 |
110 | static unsafe int ReplaceAll(byte[] data, ulong a, ulong b) {
111 | fixed (byte* p = data) {
112 | int count = 0;
113 | int length = data.Length - 7;
114 | for (int i = 0; i < length; i++) {
115 | if (*(ulong*)(p + i) == a) {
116 | *(ulong*)(p + i) = b;
117 | count++;
118 | i += 7;
119 | }
120 | }
121 | return count;
122 | }
123 | }
124 |
125 | static unsafe void ConvertToUncompressed(byte[] data) {
126 | fixed (byte* p = data) {
127 | int i = 0;
128 | int length = data.Length - 3;
129 | for (; i < length; i++) {
130 | if (*(uint*)(p + i) == 0x424A5342)
131 | break;
132 | }
133 | Debug2.Assert(i != data.Length);
134 | length = data.Length - 1;
135 | for (; i < length; i++) {
136 | if (*(ushort*)(p + i) == 0x7E23)
137 | break;
138 | }
139 | Debug2.Assert(i != data.Length);
140 | *(ushort*)(p + i) = 0x2D23;
141 | }
142 | }
143 |
144 | static byte[] Decompress(byte[] data) {
145 | var buffer = new List(data.Length * 2);
146 | for (int i = 0; i < data.Length; i++) {
147 | if (data[i] == 0xFF) {
148 | ushort count = (ushort)(data[i + 1] | (data[i + 2] << 8));
149 | if (count != 0xFFFF) {
150 | Debug2.Assert(count > 3);
151 | while (count-- > 0)
152 | buffer.Add(0x00);
153 | }
154 | else {
155 | buffer.Add(0xFF);
156 | }
157 | i += 2;
158 | }
159 | else {
160 | buffer.Add(data[i]);
161 | }
162 | }
163 | return buffer.ToArray();
164 | }
165 |
166 | static readonly byte[] data = new byte[] {
167 | 0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
168 | 0xFF, 0xFF, 0x00, 0x00, 0xB8, 0xFF, 0x07, 0x00, 0x40, 0xFF, 0x23, 0x00, 0x80, 0x00, 0x00, 0x00,
169 | 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68,
170 | 0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F,
171 | 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20,
172 | 0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x0D, 0x0D, 0x0A, 0x24, 0xFF, 0x07, 0x00, 0x50, 0x45, 0x00, 0x00,
173 | 0x4C, 0x01, 0x02, 0x00, 0xA7, 0xC5, 0x04, 0x62, 0xFF, 0x08, 0x00, 0xE0, 0x00, 0x02, 0x21, 0x0B,
174 | 0x01, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x06, 0x00, 0x1E, 0x22, 0x00, 0x00,
175 | 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0xFF, 0x04, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
176 | 0x02, 0x00, 0x00, 0x04, 0xFF, 0x07, 0x00, 0x04, 0xFF, 0x08, 0x00, 0x60, 0x00, 0x00, 0x00, 0x02,
177 | 0xFF, 0x06, 0x00, 0x03, 0x00, 0x40, 0x85, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0xFF, 0x04, 0x00,
178 | 0x10, 0x00, 0x00, 0x10, 0xFF, 0x06, 0x00, 0x10, 0xFF, 0x0B, 0x00, 0xCC, 0x21, 0x00, 0x00, 0x4F,
179 | 0xFF, 0x1C, 0x00, 0x40, 0x00, 0x00, 0x0C, 0xFF, 0x34, 0x00, 0x20, 0x00, 0x00, 0x08, 0xFF, 0x0B,
180 | 0x00, 0x08, 0x20, 0x00, 0x00, 0x48, 0xFF, 0x0B, 0x00, 0x2E, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00,
181 | 0x00, 0x24, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0xFF,
182 | 0x0E, 0x00, 0x20, 0x00, 0x00, 0x60, 0x2E, 0x72, 0x65, 0x6C, 0x6F, 0x63, 0x00, 0x00, 0x0C, 0xFF,
183 | 0x04, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0xFF, 0x0E, 0x00, 0x40, 0x00,
184 | 0x00, 0x42, 0xFF, 0x39, 0x00, 0x22, 0xFF, 0x06, 0x00, 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05,
185 | 0x00, 0x58, 0x20, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x01, 0xFF, 0x37, 0x00, 0x1E, 0x02, 0x28,
186 | 0x01, 0x00, 0x00, 0x0A, 0x2A, 0x42, 0x53, 0x4A, 0x42, 0x01, 0x00, 0x01, 0xFF, 0x05, 0x00, 0x0C,
187 | 0x00, 0x00, 0x00, 0x76, 0x32, 0x2E, 0x30, 0x2E, 0x35, 0x30, 0x37, 0x32, 0x37, 0xFF, 0x04, 0x00,
188 | 0x05, 0x00, 0x6C, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x23, 0x7E, 0x00, 0x00, 0x0C, 0x01,
189 | 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x23, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x73, 0xFF, 0x04,
190 | 0x00, 0x4C, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x23, 0x55, 0x53, 0x00, 0x54, 0x01, 0x00,
191 | 0x00, 0x10, 0x00, 0x00, 0x00, 0x23, 0x47, 0x55, 0x49, 0x44, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00,
192 | 0x00, 0x10, 0x00, 0x00, 0x00, 0x23, 0x42, 0x6C, 0x6F, 0x62, 0xFF, 0x07, 0x00, 0x02, 0x00, 0x00,
193 | 0x01, 0x47, 0x04, 0x00, 0x00, 0x09, 0xFF, 0x04, 0x00, 0xFA, 0x01, 0x33, 0x00, 0x16, 0x00, 0x00,
194 | 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
195 | 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x05, 0x00, 0x1E, 0x00, 0x01, 0xFF,
196 | 0x05, 0x00, 0x06, 0x00, 0x11, 0x00, 0x0A, 0xFF, 0x05, 0x00, 0x01, 0xFF, 0x05, 0x00, 0x01, 0x00,
197 | 0x01, 0x00, 0x01, 0x00, 0x10, 0x00, 0x34, 0x00, 0x34, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01, 0x00,
198 | 0x50, 0x20, 0xFF, 0x04, 0x00, 0x86, 0x18, 0x18, 0x00, 0x01, 0x00, 0x01, 0x00, 0x09, 0x00, 0x18,
199 | 0x00, 0x01, 0xFF, 0x13, 0x00, 0x36, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x0B, 0x00, 0x05, 0x00, 0x2B,
200 | 0xFF, 0x08, 0x00, 0x3C, 0x4D, 0x6F, 0x64, 0x75, 0x6C, 0x65, 0x3E, 0x00, 0x53, 0x79, 0x73, 0x74,
201 | 0x65, 0x6D, 0x00, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x00, 0x2E, 0x63, 0x74, 0x6F, 0x72, 0x00,
202 | 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x6D, 0x73, 0x63,
203 | 0x6F, 0x72, 0x6C, 0x69, 0x62, 0x00, 0x5F, 0x00, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F,
204 | 0x00, 0x00, 0x00, 0x03, 0x20, 0xFF, 0x05, 0x00, 0x83, 0xBA, 0x7D, 0x5E, 0x24, 0xF6, 0x28, 0x4D,
205 | 0x9C, 0xE0, 0x95, 0x2F, 0xF7, 0x42, 0xFE, 0xF4, 0x00, 0x03, 0x20, 0x00, 0x01, 0x08, 0xB7, 0x7A,
206 | 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89, 0x00, 0x00, 0xF4, 0x21, 0xFF, 0x0A, 0x00, 0x0E, 0x22, 0x00,
207 | 0x00, 0x00, 0x20, 0xFF, 0x17, 0x00, 0x22, 0xFF, 0x0C, 0x00, 0x5F, 0x43, 0x6F, 0x72, 0x44, 0x6C,
208 | 0x6C, 0x4D, 0x61, 0x69, 0x6E, 0x00, 0x6D, 0x73, 0x63, 0x6F, 0x72, 0x65, 0x65, 0x2E, 0x64, 0x6C,
209 | 0x6C, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0xFF, 0x25, 0x00, 0x20, 0x40, 0xFF, 0xDE, 0x01, 0x20, 0x00,
210 | 0x00, 0x0C, 0x00, 0x00, 0x00, 0x20, 0x32, 0xFF, 0xF6, 0x01
211 | };
212 | }
213 |
214 | //.assembly extern mscorlib
215 | //{
216 | // .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
217 | // .ver 2:0:0:0
218 | //}
219 | //.assembly '________'
220 | //{
221 | //}
222 | //.module '________.dll'
223 | //.class public auto ansi beforefieldinit _._ extends [mscorlib]System.Object
224 | //{
225 | // .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
226 | // {
227 | // .maxstack 8
228 | // IL_0000: ldarg.0
229 | // IL_0001: call instance void [mscorlib]System.Object::.ctor()
230 | // IL_0006: ret
231 | // }
232 | //}
233 |
234 | //static byte[] Compress(byte[] data) {
235 | // var buffer = new List(data.Length);
236 | // for (int i = 0; i < data.Length; i++) {
237 | // if (data[i] == 0) {
238 | // int count = CountZero(data, i);
239 | // i += count - 1;
240 | // if (count > 3) {
241 | // Debug2.Assert(count < 0xFFFF);
242 | // buffer.Add(0xFF);
243 | // buffer.Add((byte)count);
244 | // buffer.Add((byte)(count >> 8));
245 | // }
246 | // else {
247 | // while (count-- > 0)
248 | // buffer.Add(0x00);
249 | // }
250 | // }
251 | // else if (data[i] == 0xFF) {
252 | // buffer.Add(0xFF);
253 | // buffer.Add(0xFF);
254 | // buffer.Add(0xFF);
255 | // }
256 | // else {
257 | // buffer.Add(data[i]);
258 | // }
259 | // }
260 | // return buffer.ToArray();
261 |
262 | // static int CountZero(byte[] data, int index) {
263 | // int i = index;
264 | // for (; i < data.Length && data[i] == 0; i++) ;
265 | // return i - index;
266 | // }
267 | //}
268 |
--------------------------------------------------------------------------------
/MetadataLocator/RuntimeDefinitions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace MetadataLocator;
4 |
5 | #pragma warning disable CS0649
6 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
7 | public static unsafe class RuntimeDefinitions {
8 | static class DummyBuffer {
9 | public static readonly nuint Value = VirtualAlloc(0, 0x2000, 0x1000, 4);
10 |
11 | [DllImport("kernel32.dll", SetLastError = true)]
12 | static extern nuint VirtualAlloc(nuint lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
13 | }
14 |
15 | #region Basic
16 | public const uint STORAGE_MAGIC_SIG = 0x424A5342; // BSJB
17 |
18 | public struct IMAGE_DATA_DIRECTORY {
19 | public uint VirtualAddress;
20 | public uint Size;
21 | }
22 |
23 | public struct IMAGE_COR20_HEADER {
24 | public uint cb;
25 | public ushort MajorRuntimeVersion;
26 | public ushort MinorRuntimeVersion;
27 | public IMAGE_DATA_DIRECTORY MetaData;
28 | public uint Flags;
29 | public uint EntryPointTokenOrRVA;
30 | public IMAGE_DATA_DIRECTORY Resources;
31 | public IMAGE_DATA_DIRECTORY StrongNameSignature;
32 | public IMAGE_DATA_DIRECTORY CodeManagerTable;
33 | public IMAGE_DATA_DIRECTORY VTableFixups;
34 | public IMAGE_DATA_DIRECTORY ExportAddressTableJumps;
35 | public IMAGE_DATA_DIRECTORY ManagedNativeHeader;
36 | };
37 |
38 | public unsafe struct STORAGESIGNATURE {
39 | public uint lSignature; // "Magic" signature.
40 | public ushort iMajorVer; // Major file version.
41 | public ushort iMinorVer; // Minor file version.
42 | public uint iExtraData; // Offset to next structure of information
43 | public uint iVersionString; // Length of version string
44 | public fixed byte pVersion[1]; // Version string
45 | }
46 |
47 | public struct STORAGEHEADER {
48 | public byte fFlags;
49 | public byte pad;
50 | public ushort iStreams;
51 | }
52 |
53 | // ==========================================================================================
54 | // SString is the base class for safe strings.
55 | // ==========================================================================================
56 | /* complete, 0x10 / 0x18 bytes */
57 | public struct SString {
58 | /* important */
59 | public uint m_size;
60 | public uint m_allocation;
61 | public uint m_flags;
62 | /* important */
63 | public nuint m_buffer;
64 | }
65 |
66 | /* complete, 0x1c / 0x30 bytes */
67 | public struct Crst {
68 | public nuint __padding_0;
69 | public uint __padding_1;
70 | public uint __padding_2;
71 | public nuint __padding_3;
72 | public nuint __padding_4;
73 | public nuint __padding_5;
74 | public uint __padding_6;
75 | }
76 | #endregion
77 |
78 | #region PE
79 | //public const int IMAGE_FLAT = 0;
80 | //public const int IMAGE_MAPPED = 1; // comment: not existing in .NET ? TODO: update comment when .NET 7.0 released https://github.com/dotnet/runtime/commit/35e4e97867db6bb2cc1c9f1e91c80dd80759e259#diff-42902be0f805f8e1cce666fd0dfb892ffbac53d3c5897beaa86d965df22ef1dbL287
81 | //public const int IMAGE_LOADED = 2;
82 | //public const int IMAGE_LOADED_FOR_INTROSPECTION = 3; // comment: not existing in .NET Core v3.0+ https://github.com/dotnet/coreclr/commit/af4ec7c89d0192ad14392da04e8c097da8ec9e48#diff-dd1e605d2e73125b21c2617d94bb41def043971508334410a1d62675cf768b6dL332
83 | //public const int IMAGE_COUNT = 4;
84 |
85 | // --------------------------------------------------------------------------------
86 | // PEDecoder - Utility class for reading and verifying PE files.
87 | //
88 | // Note that the Check step is optional if you are willing to trust the
89 | // integrity of the image.
90 | // (Or at any rate can be factored into an initial verification step.)
91 | //
92 | // Functions which access the memory of the PE file take a "flat" flag - this
93 | // indicates whether the PE images data has been loaded flat the way it resides in the file,
94 | // or if the sections have been mapped into memory at the proper base addresses.
95 | //
96 | // Finally, some functions take an optional "size" argument, which can be used for
97 | // range verification. This is an optional parameter, but if you omit it be sure
98 | // you verify the size in some other way.
99 | // --------------------------------------------------------------------------------
100 | /* complete, 0x18 / 0x28 bytes */
101 | public struct PEDecoder {
102 | /* important */
103 | public nuint m_base;
104 | /* important */
105 | public uint m_size; // size of file on disk, as opposed to OptionalHeaders.SizeOfImage
106 | public uint m_flags;
107 | public nuint m_pNTHeaders;
108 | /* important */
109 | public nuint m_pCorHeader;
110 | public nuint m_pNativeHeader;
111 | // public nuint m_pReadyToRunHeader; // comment: only in coreclr
112 | }
113 |
114 | /* incomplete */
115 | public struct PEImageLayout {
116 | public static readonly PEImageLayout* Dummy = (PEImageLayout*)DummyBuffer.Value;
117 |
118 | public nuint __vfptr;
119 | public PEDecoder __base;
120 |
121 | // ... some paddings ...
122 | }
123 |
124 | // --------------------------------------------------------------------------------
125 | // PEImage is a PE file loaded by our "simulated LoadLibrary" mechanism. A PEImage
126 | // can be loaded either FLAT (same layout as on disk) or MAPPED (PE sections
127 | // mapped into virtual addresses.)
128 | //
129 | // The MAPPED format is currently limited to "IL only" images - this can be checked
130 | // for via PEDecoder::IsILOnlyImage.
131 | //
132 | // NOTE: PEImage will NEVER call LoadLibrary.
133 | // --------------------------------------------------------------------------------
134 | /* incomplete */
135 | public struct PEImage {
136 | }
137 |
138 | /* incomplete */
139 | public struct PEImage_20 {
140 | public static readonly PEImage_20* Dummy = (PEImage_20*)DummyBuffer.Value;
141 |
142 | public Crst m_PdbStreamLock;
143 | public nuint m_pPdbStream;
144 | /* important */
145 | public SString m_path;
146 |
147 | // ... some paddings ...
148 | /* important */
149 | //public fixed nuint m_pLayouts[IMAGE_COUNT];
150 | //public bool m_bInHashMap;
151 | //public nuint m_pMDTracker; // comments: might exists
152 | /* important */
153 | //public nuin m_pMDImport;
154 | }
155 |
156 | /* incomplete */
157 | public struct PEImage_40 {
158 | public static readonly PEImage_40* Dummy = (PEImage_40*)DummyBuffer.Value;
159 |
160 | /* important */
161 | public SString m_path;
162 |
163 | // ... some paddings ...
164 | /* important */
165 | //public fixed nuint m_pLayouts[IMAGE_COUNT];
166 | //public bool m_bInHashMap;
167 | //public nuint m_pMDTracker; // comments: might exists
168 | /* important */
169 | //public nuin m_pMDImport;
170 | }
171 |
172 | /* incomplete */
173 | public struct PEFile {
174 | public static readonly PEFile* Dummy = (PEFile*)DummyBuffer.Value;
175 |
176 | public nuint __vfptr;
177 | public PEImage* m_identity; // Identity image
178 | /* important */
179 | public PEImage* m_openedILimage; // IL image, NULL if we didn't need to open the file
180 |
181 | // ... some paddings ...
182 | /* important */
183 | //public nuint m_pMDImport;
184 | }
185 | #endregion
186 |
187 | #region StgPool
188 | /* complete, 0x10 / 0x18 bytes */
189 | public struct StgPoolSeg {
190 | /* important */
191 | public byte* m_pSegData; // Pointer to the data.
192 | public StgPoolSeg* m_pNextSeg; // Pointer to next segment, or NULL.
193 | /* important */
194 | public uint m_cbSegSize; // Size of the segment buffer. If this is last segment (code:m_pNextSeg is NULL), then it's the
195 | // allocation size. If this is not the last segment, then this is shrinked to segment data size
196 | // (code:m_cbSegNext).
197 | public uint m_cbSegNext; // Offset of next available byte in segment. Segment relative.
198 | }
199 |
200 | /* complete, 0x18 / 0x28 bytes */
201 | public struct StgPoolReadOnly {
202 | public nuint __vfptr;
203 | public StgPoolSeg __base;
204 | public nuint m_HotHeap;
205 | }
206 |
207 | /* complete, 0x18 / 0x28 bytes */
208 | public struct StgBlobPoolReadOnly {
209 | public StgPoolReadOnly __base;
210 | }
211 |
212 | //public struct StgPool {
213 | // public StgPoolReadOnly __base;
214 | // public uint m_ulGrowInc; // How many bytes at a time.
215 | // public StgPoolSeg* m_pCurSeg; // Current seg for append -- end of chain.
216 | // public uint m_cbCurSegOffset; // Base offset of current seg.
217 | // public uint m_bFree_or_bReadOnly; // True if we should free base data. Extension data is always freed.
218 | // // True if we shouldn't append.
219 | // public uint m_nVariableAlignmentMask; // Alignment mask (variable 0, 1 or 3).
220 | // public uint m_cbStartOffsetOfEdit; // Place in the pool where edits started
221 | // public int m_fValidOffsetOfEdit; // Is the pool edit offset valid
222 | //}
223 | #endregion
224 |
225 | #region Metadata Table
226 | public const int TBL_Module = 0;
227 | public const int TBL_TypeRef = 1;
228 | public const int TBL_TypeDef = 2;
229 | public const int TBL_FieldPtr = 3;
230 | public const int TBL_Field = 4;
231 | public const int TBL_MethodPtr = 5;
232 | public const int TBL_Method = 6;
233 | public const int TBL_ParamPtr = 7;
234 | public const int TBL_Param = 8;
235 | public const int TBL_InterfaceImpl = 9;
236 | public const int TBL_MemberRef = 10;
237 | public const int TBL_Constant = 11;
238 | public const int TBL_CustomAttribute = 12;
239 | public const int TBL_FieldMarshal = 13;
240 | public const int TBL_DeclSecurity = 14;
241 | public const int TBL_ClassLayout = 15;
242 | public const int TBL_FieldLayout = 16;
243 | public const int TBL_StandAloneSig = 17;
244 | public const int TBL_EventMap = 18;
245 | public const int TBL_EventPtr = 19;
246 | public const int TBL_Event = 20;
247 | public const int TBL_PropertyMap = 21;
248 | public const int TBL_PropertyPtr = 22;
249 | public const int TBL_Property = 23;
250 | public const int TBL_MethodSemantics = 24;
251 | public const int TBL_MethodImpl = 25;
252 | public const int TBL_ModuleRef = 26;
253 | public const int TBL_TypeSpec = 27;
254 | public const int TBL_ImplMap = 28;
255 | public const int TBL_FieldRVA = 29;
256 | public const int TBL_ENCLog = 30;
257 | public const int TBL_ENCMap = 31;
258 | public const int TBL_Assembly = 32;
259 | public const int TBL_AssemblyProcessor = 33;
260 | public const int TBL_AssemblyOS = 34;
261 | public const int TBL_AssemblyRef = 35;
262 | public const int TBL_AssemblyRefProcessor = 36;
263 | public const int TBL_AssemblyRefOS = 37;
264 | public const int TBL_File = 38;
265 | public const int TBL_ExportedType = 39;
266 | public const int TBL_ManifestResource = 40;
267 | public const int TBL_NestedClass = 41;
268 | public const int TBL_GenericParam = 42;
269 | public const int TBL_MethodSpec = 43;
270 | public const int TBL_GenericParamConstraint = 44;
271 | public const int TBL_COUNT = 45; // Highest table.
272 | public const int TBL_COUNT_V1 = 42; // Highest table in v1.0 database
273 | public const int TBL_COUNT_V2 = 45; // Highest in v2.0 database
274 |
275 | /* complete, 0x04 / 0x08 bytes */
276 | public struct TableRO {
277 | public nuint m_pData;
278 | }
279 |
280 | /* complete, 0x00b4 / 0x0168 bytes */
281 | public struct TableROs {
282 | public TableRO Module;
283 | public TableRO TypeRef;
284 | public TableRO TypeDef;
285 | public TableRO FieldPtr;
286 | public TableRO Field;
287 | public TableRO MethodPtr;
288 | public TableRO Method;
289 | public TableRO ParamPtr;
290 | public TableRO Param;
291 | public TableRO InterfaceImpl;
292 | public TableRO MemberRef;
293 | public TableRO Constant;
294 | public TableRO CustomAttribute;
295 | public TableRO FieldMarshal;
296 | public TableRO DeclSecurity;
297 | public TableRO ClassLayout;
298 | public TableRO FieldLayout;
299 | public TableRO StandAloneSig;
300 | public TableRO EventMap;
301 | public TableRO EventPtr;
302 | public TableRO Event;
303 | public TableRO PropertyMap;
304 | public TableRO PropertyPtr;
305 | public TableRO Property;
306 | public TableRO MethodSemantics;
307 | public TableRO MethodImpl;
308 | public TableRO ModuleRef;
309 | public TableRO TypeSpec;
310 | public TableRO ImplMap;
311 | public TableRO FieldRVA;
312 | public TableRO ENCLog;
313 | public TableRO ENCMap;
314 | public TableRO Assembly;
315 | public TableRO AssemblyProcessor;
316 | public TableRO AssemblyOS;
317 | public TableRO AssemblyRef;
318 | public TableRO AssemblyRefProcessor;
319 | public TableRO AssemblyRefOS;
320 | public TableRO File;
321 | public TableRO ExportedType;
322 | public TableRO ManifestResource;
323 | public TableRO NestedClass;
324 | public TableRO GenericParam;
325 | public TableRO MethodSpec;
326 | public TableRO GenericParamConstraint;
327 | }
328 | #endregion
329 |
330 | #region Metadata Heap
331 | /* complete, 0x18 / 0x28 bytes */
332 | public struct StringHeapRO {
333 | public StgPoolReadOnly m_StringPool;
334 | }
335 |
336 | /* complete, 0x18 / 0x28 bytes */
337 | public struct BlobHeapRO {
338 | public StgBlobPoolReadOnly m_BlobPool;
339 | }
340 |
341 | /* complete, 0x18 / 0x28 bytes */
342 | public struct GuidHeapRO {
343 | public StgPoolReadOnly m_GuidPool;
344 | }
345 | #endregion
346 |
347 | #region Metadata Model
348 | //*****************************************************************************
349 | // The mini, hard-coded schema. For each table, we persist the count of
350 | // records. We also persist the size of string, blob, guid, and rid
351 | // columns. From this information, we can calculate the record sizes, and
352 | // then the sizes of the tables.
353 | //*****************************************************************************
354 | /* complete, 0x18 / 0x18 bytes */
355 | public struct CMiniMdSchemaBase {
356 | public uint m_ulReserved; // Reserved, must be zero.
357 | public byte m_major; // Version numbers.
358 | public byte m_minor;
359 | public byte m_heaps; // Bits for heap sizes.
360 | public byte m_rid; // log-base-2 of largest rid.
361 | public ulong m_maskvalid; // Bit mask of present table counts.
362 | public ulong m_sorted; // Bit mask of sorted tables.
363 | }
364 |
365 | /* complete, 0xd0 / 0xd0 bytes */
366 | public struct CMiniMdSchema {
367 | public CMiniMdSchemaBase __base;
368 | public fixed uint m_cRecs[TBL_COUNT]; // Counts of various tables.
369 | public uint m_ulExtra; // Extra data, only persisted if non-zero. (m_heaps&EXTRA_DATA flags)
370 | }
371 |
372 | /* complete, 0x03 / 0x03 bytes */
373 | public struct CMiniColDef {
374 | public byte m_Type; // Type of the column.
375 | public byte m_oColumn; // Offset of the column.
376 | public byte m_cbColumn; // Size of the column.
377 | };
378 |
379 | /* complete, 0x08 / 0x10 bytes */
380 | public struct CMiniTableDef {
381 | public CMiniColDef* m_pColDefs; // Array of field defs.
382 | public byte m_cCols; // Count of columns in the table.
383 | public byte m_iKey; // Column which is the key, if any.
384 | public ushort m_cbRec; // Size of the records.
385 | };
386 |
387 | /* complete, 0x0168 / 0x02d0 bytes */
388 | public struct CMiniTableDefs {
389 | public CMiniTableDef Module;
390 | public CMiniTableDef TypeRef;
391 | public CMiniTableDef TypeDef;
392 | public CMiniTableDef FieldPtr;
393 | public CMiniTableDef Field;
394 | public CMiniTableDef MethodPtr;
395 | public CMiniTableDef Method;
396 | public CMiniTableDef ParamPtr;
397 | public CMiniTableDef Param;
398 | public CMiniTableDef InterfaceImpl;
399 | public CMiniTableDef MemberRef;
400 | public CMiniTableDef Constant;
401 | public CMiniTableDef CustomAttribute;
402 | public CMiniTableDef FieldMarshal;
403 | public CMiniTableDef DeclSecurity;
404 | public CMiniTableDef ClassLayout;
405 | public CMiniTableDef FieldLayout;
406 | public CMiniTableDef StandAloneSig;
407 | public CMiniTableDef EventMap;
408 | public CMiniTableDef EventPtr;
409 | public CMiniTableDef Event;
410 | public CMiniTableDef PropertyMap;
411 | public CMiniTableDef PropertyPtr;
412 | public CMiniTableDef Property;
413 | public CMiniTableDef MethodSemantics;
414 | public CMiniTableDef MethodImpl;
415 | public CMiniTableDef ModuleRef;
416 | public CMiniTableDef TypeSpec;
417 | public CMiniTableDef ImplMap;
418 | public CMiniTableDef FieldRVA;
419 | public CMiniTableDef ENCLog;
420 | public CMiniTableDef ENCMap;
421 | public CMiniTableDef Assembly;
422 | public CMiniTableDef AssemblyProcessor;
423 | public CMiniTableDef AssemblyOS;
424 | public CMiniTableDef AssemblyRef;
425 | public CMiniTableDef AssemblyRefProcessor;
426 | public CMiniTableDef AssemblyRefOS;
427 | public CMiniTableDef File;
428 | public CMiniTableDef ExportedType;
429 | public CMiniTableDef ManifestResource;
430 | public CMiniTableDef NestedClass;
431 | public CMiniTableDef GenericParam;
432 | public CMiniTableDef MethodSpec;
433 | public CMiniTableDef GenericParamConstraint;
434 | }
435 |
436 | /* complete, 0x0250 / 0x03c0 bytes */
437 | public struct CMiniMdBase_20 {
438 | public static readonly CMiniMdBase_20* Dummy = (CMiniMdBase_20*)DummyBuffer.Value;
439 |
440 | public nuint __vfptr;
441 | public CMiniMdSchema m_Schema; // data header.
442 | public uint m_TblCount; // Tables in this database.
443 | public CMiniTableDefs m_TableDefs;
444 | public uint m_iStringsMask;
445 | public uint m_iGuidsMask;
446 | public uint m_iBlobsMask;
447 | }
448 |
449 | /* complete, 0x0258 / 0x03c0 bytes */
450 | public struct CMiniMdBase_40 {
451 | public static readonly CMiniMdBase_40* Dummy = (CMiniMdBase_40*)DummyBuffer.Value;
452 |
453 | public nuint __vfptr;
454 | public CMiniMdSchema m_Schema; // data header.
455 | public uint m_TblCount; // Tables in this database.
456 | public int m_fVerifiedByTrustedSource; // whether the data was verified by a trusted source
457 | public CMiniTableDefs m_TableDefs;
458 | public uint m_iStringsMask;
459 | public uint m_iGuidsMask;
460 | public uint m_iBlobsMask;
461 | }
462 |
463 | /* complete, 0x0368 / 0x05c8 bytes */
464 | public struct CMiniMd_20 {
465 | public CMiniMdBase_20 __base;
466 | public TableROs m_Tables;
467 | public GuidHeapRO m_GuidHeap;
468 | public StringHeapRO m_StringHeap;
469 | public BlobHeapRO m_BlobHeap;
470 | public BlobHeapRO m_UserStringHeap;
471 | }
472 |
473 | /* complete, 0x0370 / 0x05c8 bytes */
474 | public struct CMiniMd_40 {
475 | public CMiniMdBase_40 __base;
476 | public TableROs m_Tables;
477 | public StringHeapRO m_StringHeap;
478 | public BlobHeapRO m_BlobHeap;
479 | public BlobHeapRO m_UserStringHeap;
480 | public GuidHeapRO m_GuidHeap;
481 | }
482 |
483 | public struct CMiniMdRW {
484 | //public CMiniMdBase __base;
485 |
486 | // ... some paddings ...
487 | }
488 | #endregion
489 |
490 | #region LiteWeightStgdb
491 | /* complete, 0x0370 / 0x05d8 bytes */
492 | public struct CLiteWeightStgdb_CMiniMd_20 {
493 | public CMiniMd_20 m_MiniMd;
494 | public nuint m_pvMd;
495 | public uint m_cbMd;
496 | }
497 |
498 | /* complete, 0x0378 / 0x05d8 bytes */
499 | public struct CLiteWeightStgdb_CMiniMd_40 {
500 | public CMiniMd_40 m_MiniMd;
501 | public nuint m_pvMd;
502 | public uint m_cbMd;
503 | }
504 |
505 | public struct CLiteWeightStgdb_CMiniMdRW {
506 | public CMiniMdRW m_MiniMd;
507 |
508 | // ... some paddings ...
509 | //public nuint m_pvMd;
510 | //public uint m_cbMd;
511 | }
512 |
513 | public struct CLiteWeightStgdbRW {
514 | public CLiteWeightStgdb_CMiniMdRW __base;
515 |
516 | // ... some paddings ...
517 | }
518 | #endregion
519 |
520 | #region Metadata Internal
521 | public struct MDInternalRO {
522 | }
523 |
524 | /* complete, 0x0378 / 0x05e0 bytes */
525 | public struct MDInternalRO_20 {
526 | public nuint __vfptr_IMDInternalImport;
527 | public CLiteWeightStgdb_CMiniMd_20 m_LiteWeightStgdb;
528 | }
529 |
530 | /* complete, 0x0380 / 0x05e0 bytes */
531 | public struct MDInternalRO_40 {
532 | public nuint __vfptr_IMDInternalImport;
533 | public CLiteWeightStgdb_CMiniMd_40 m_LiteWeightStgdb;
534 | }
535 |
536 | /* complete, 0x0380 / 0x05e0 bytes */
537 | public struct MDInternalRO_45 {
538 | public nuint __vfptr_IMDInternalImport;
539 | public nuint __vfptr_IMDCommon;
540 | public CLiteWeightStgdb_CMiniMd_40 m_LiteWeightStgdb;
541 | }
542 |
543 | public struct MDInternalRW {
544 | }
545 |
546 | public struct MDInternalRW_20 {
547 | public static readonly MDInternalRW_20* Dummy = (MDInternalRW_20*)DummyBuffer.Value;
548 |
549 | public nuint __vfptr_IMDInternalImport;
550 | public CLiteWeightStgdbRW* m_pStgdb;
551 | }
552 |
553 | public struct MDInternalRW_45 {
554 | public static readonly MDInternalRW_45* Dummy = (MDInternalRW_45*)DummyBuffer.Value;
555 |
556 | public nuint __vfptr_IMDInternalImport;
557 | public nuint __vfptr_IMDCommon;
558 | public CLiteWeightStgdbRW* m_pStgdb;
559 | }
560 | #endregion
561 |
562 | #region Reflection
563 | /* incomplete */
564 | public struct Module {
565 | }
566 |
567 | /* incomplete */
568 | public struct Module_20 {
569 | public static readonly Module_20* Dummy = (Module_20*)DummyBuffer.Value;
570 |
571 | public nuint __vfptr;
572 | /* important */
573 | public PEFile* m_file;
574 | }
575 |
576 | /* incomplete */
577 | public struct Module_453 {
578 | public static readonly Module_453* Dummy = (Module_453*)DummyBuffer.Value;
579 |
580 | public nuint __vfptr;
581 | public nuint m_pSimpleName;
582 | /* important */
583 | public PEFile* m_file;
584 | }
585 | #endregion
586 | }
587 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
588 | #pragma warning restore CS0649
589 |
--------------------------------------------------------------------------------
/MetadataLocator/MetadataInfoImpl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using static MetadataLocator.RuntimeDefinitions;
4 | using SR = System.Reflection;
5 |
6 | namespace MetadataLocator;
7 |
8 | static unsafe class MetadataInfoImpl {
9 | sealed class Profile {
10 | public nuint Vfptr; // MDInternalRO or MDInternalRW
11 | public bool Uncompressed;
12 | public bool RidAsIndex;
13 | public Pointer Schema = Pointer.Empty;
14 | public Pointer TableCount = Pointer.Empty;
15 | public Pointer TableDefs = Pointer.Empty;
16 | public Pointer MetadataAddress = Pointer.Empty;
17 | public Pointer MetadataSize = Pointer.Empty;
18 | public Pointer[] HeapAddress = Array2.Empty();
19 | public Pointer[] HeapSize = Array2.Empty();
20 | public Pointer TableAddress = Pointer.Empty;
21 | public uint NextTableOffset;
22 | }
23 |
24 | const int StringHeapIndex = 0;
25 | const int UserStringsHeapIndex = 1;
26 | const int GuidHeapIndex = 2;
27 | const int BlobHeapIndex = 3;
28 |
29 | static Profile[] profiles = Array2.Empty();
30 | static bool isInitialized;
31 |
32 | static void Initialize() {
33 | if (isInitialized)
34 | return;
35 |
36 | profiles = new Profile[2];
37 | profiles[0] = CreateProfile(false);
38 | profiles[1] = CreateProfile(true);
39 |
40 | isInitialized = true;
41 | }
42 |
43 | static Profile CreateProfile(bool uncompressed) {
44 | const bool InMemory = false;
45 |
46 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
47 | if (uncompressed)
48 | assemblyFlags |= TestAssemblyFlags.Uncompressed;
49 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
50 | nuint module = assembly.ModuleHandle;
51 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
52 | // Get native Module object
53 |
54 | var stgdbPointer = ScanLiteWeightStgdbPointer(uncompressed, out nuint vfptr);
55 | ScanMetadataOffsets(stgdbPointer, uncompressed, out uint metadataAddressOffset, out uint metadataSizeOffset);
56 | var info = new MiniMetadataInfo(Utils.ReadUIntPtr(Utils.WithOffset(stgdbPointer, metadataAddressOffset), module));
57 | ScanSchemaOffset(stgdbPointer, info, uncompressed, out uint schemaOffset);
58 | ScanTableDefsOffsets(stgdbPointer, uncompressed, schemaOffset, out uint tableCountOffset, out uint tableDefsOffset);
59 | ScanHeapOffsets(stgdbPointer, info, uncompressed, out var heapAddressOffsets, out var heapSizeOffsets);
60 | bool ridAsIndex = RuntimeEnvironment.Version < RuntimeVersion.Fx40 && !uncompressed;
61 | // see sscli2 CMiniMd::SetTablePointers, Set the pointers to consecutive areas of a large buffer.
62 | info.CalculateTableAddress(Utils.ReadPointer(Utils.WithOffset(stgdbPointer, tableDefsOffset), module), ridAsIndex);
63 | ScanTableOffset(stgdbPointer, info, uncompressed, out uint tableAddressOffset, out uint nextTableOffset);
64 | var profile = new Profile {
65 | Vfptr = vfptr,
66 | Uncompressed = uncompressed,
67 | RidAsIndex = ridAsIndex,
68 | Schema = Utils.WithOffset(stgdbPointer, schemaOffset),
69 | TableCount = Utils.WithOffset(stgdbPointer, tableCountOffset),
70 | TableDefs = Utils.WithOffset(stgdbPointer, tableDefsOffset),
71 | MetadataAddress = Utils.WithOffset(stgdbPointer, metadataAddressOffset),
72 | MetadataSize = Utils.WithOffset(stgdbPointer, metadataSizeOffset),
73 | HeapAddress = Utils.WithOffset(stgdbPointer, heapAddressOffsets),
74 | HeapSize = Utils.WithOffset(stgdbPointer, heapSizeOffsets),
75 | TableAddress = Utils.WithOffset(stgdbPointer, tableAddressOffset),
76 | NextTableOffset = nextTableOffset
77 | };
78 | return profile;
79 | }
80 |
81 | // Not really a CLiteWeightStgdb, for compressed metadata it returens
82 | static Pointer ScanLiteWeightStgdbPointer(bool uncompressed, out nuint vfptr) {
83 | const bool InMemory = false;
84 |
85 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
86 | if (uncompressed)
87 | assemblyFlags |= TestAssemblyFlags.Uncompressed;
88 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
89 | nuint module = assembly.ModuleHandle;
90 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
91 | // Get native Module object
92 |
93 | uint m_file_Offset;
94 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453)
95 | m_file_Offset = (uint)((nuint)(&Module_453.Dummy->m_file) - (nuint)Module_453.Dummy);
96 | else
97 | m_file_Offset = (uint)((nuint)(&Module_20.Dummy->m_file) - (nuint)Module_20.Dummy);
98 | nuint m_file = *(nuint*)(module + m_file_Offset);
99 | Utils.Check((PEFile*)m_file);
100 | // Module.m_file
101 |
102 | var metadataImport = MetadataImport.Create(assembly.Module);
103 | vfptr = metadataImport.Vfptr;
104 | nuint m_pMDImport = metadataImport.This;
105 | uint m_pMDImport_Offset;
106 | bool found = false;
107 | for (m_pMDImport_Offset = 0; m_pMDImport_Offset < 8 * (uint)sizeof(nuint); m_pMDImport_Offset += 4) {
108 | if (*(nuint*)(m_file + m_pMDImport_Offset) != m_pMDImport)
109 | continue;
110 | found = true;
111 | break;
112 | }
113 | Utils.Check(found);
114 | // PEFile.m_pMDImport
115 |
116 | uint m_pStgdb_Offset = 0;
117 | if (uncompressed) {
118 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx45)
119 | m_pStgdb_Offset = (uint)((nuint)(&MDInternalRW_45.Dummy->m_pStgdb) - (nuint)MDInternalRW_45.Dummy);
120 | else
121 | m_pStgdb_Offset = (uint)((nuint)(&MDInternalRW_20.Dummy->m_pStgdb) - (nuint)MDInternalRW_20.Dummy);
122 | }
123 | // MDInternalRW.m_pStgdb
124 |
125 | var pointer = new Pointer(new[] {
126 | m_file_Offset,
127 | m_pMDImport_Offset
128 | });
129 | if (m_pStgdb_Offset != 0)
130 | pointer.Add(m_pStgdb_Offset);
131 | Utils.Check(Utils.Verify(pointer, uncompressed, p => Memory.TryReadUInt32(p, out _)));
132 | return pointer;
133 | }
134 |
135 | static void ScanMetadataOffsets(Pointer stgdbPointer, bool uncompressed, out uint metadataAddressOffset, out uint metadataSizeOffset) {
136 | const bool InMemory = false;
137 |
138 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
139 | if (uncompressed)
140 | assemblyFlags |= TestAssemblyFlags.Uncompressed;
141 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
142 | nuint module = assembly.ModuleHandle;
143 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
144 | // Get native Module object
145 |
146 | nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module);
147 | var peInfo = PEInfo.Create(assembly.Module);
148 | var imageLayout = peInfo.MappedLayout.IsInvalid ? peInfo.LoadedLayout : peInfo.MappedLayout;
149 | var m_pCorHeader = (IMAGE_COR20_HEADER*)imageLayout.CorHeaderAddress;
150 | nuint m_pvMd = imageLayout.ImageBase + m_pCorHeader->MetaData.VirtualAddress;
151 | uint m_cbMd = uncompressed ? 0x1c : m_pCorHeader->MetaData.Size;
152 | // *pcb = sizeof(STORAGESIGNATURE) + pStorage->GetVersionStringLength();
153 | // TODO: we should calculate actual metadata size for uncompressed metadata
154 | uint start = uncompressed ? (sizeof(nuint) == 4 ? 0x1000u : 0x19A0) : (sizeof(nuint) == 4 ? 0x350u : 0x5B0);
155 | uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x1200u : 0x1BA0) : (sizeof(nuint) == 4 ? 0x39Cu : 0x5FC);
156 | uint m_pvMd_Offset = 0;
157 | for (uint offset = start; offset <= end; offset += 4) {
158 | if (*(nuint*)(pStgdb + offset) != m_pvMd)
159 | continue;
160 | if (*(uint*)(pStgdb + offset + (uint)sizeof(nuint)) != m_cbMd)
161 | continue;
162 | m_pvMd_Offset = offset;
163 | break;
164 | }
165 | Utils.Check(m_pvMd_Offset != 0);
166 |
167 | Utils.Check(Utils.Verify(Utils.WithOffset(stgdbPointer, m_pvMd_Offset), uncompressed, p => Memory.TryReadUInt32(p, out uint signature) && signature == 0x424A5342));
168 | metadataAddressOffset = m_pvMd_Offset;
169 | metadataSizeOffset = m_pvMd_Offset + (uint)sizeof(nuint);
170 | }
171 |
172 | static void ScanSchemaOffset(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint schemaOffset) {
173 | const bool InMemory = false;
174 |
175 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
176 | if (uncompressed)
177 | assemblyFlags |= TestAssemblyFlags.Uncompressed;
178 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
179 | nuint module = assembly.ModuleHandle;
180 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
181 | // Get native Module object
182 |
183 | nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module);
184 | for (schemaOffset = 0; schemaOffset < 0x30; schemaOffset += 4) {
185 | if (*(ulong*)(pStgdb + schemaOffset) != info.Header1)
186 | continue;
187 | if (*(ulong*)(pStgdb + schemaOffset + 0x08) != info.ValidMask)
188 | continue;
189 | if (*(ulong*)(pStgdb + schemaOffset + 0x10) != info.SortedMask)
190 | continue;
191 | break;
192 | }
193 | Utils.Check(schemaOffset != 0x30);
194 | // CMiniMdBase.m_Schema
195 | }
196 |
197 | static void ScanTableDefsOffsets(Pointer stgdbPointer, bool uncompressed, uint schemaOffset, out uint tableCountOffset, out uint tableDefsOffset) {
198 | const bool InMemory = false;
199 |
200 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
201 | if (uncompressed)
202 | assemblyFlags |= TestAssemblyFlags.Uncompressed;
203 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
204 | nuint module = assembly.ModuleHandle;
205 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
206 | // Get native Module object
207 |
208 | nuint pSchema = Utils.ReadPointer(Utils.WithOffset(stgdbPointer, schemaOffset), module);
209 | nuint p = pSchema + (uint)sizeof(CMiniMdSchema);
210 | uint m_TblCount = *(uint*)p;
211 | tableCountOffset = schemaOffset + (uint)(p - pSchema);
212 | Utils.Check(m_TblCount == TBL_COUNT_V1 || m_TblCount == TBL_COUNT_V2);
213 | // CMiniMdBase.m_TblCount
214 |
215 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx40)
216 | p += (uint)((nuint)(&CMiniMdBase_40.Dummy->m_TableDefs) - (nuint)(&CMiniMdBase_40.Dummy->m_TblCount));
217 | else
218 | p += (uint)((nuint)(&CMiniMdBase_20.Dummy->m_TableDefs) - (nuint)(&CMiniMdBase_20.Dummy->m_TblCount));
219 | tableDefsOffset = schemaOffset + (uint)(p - pSchema);
220 | var m_TableDefs = (CMiniTableDef*)p;
221 | for (int i = 0; i < TBL_COUNT; i++)
222 | Utils.Check(Memory.TryReadUInt32((nuint)m_TableDefs[i].m_pColDefs, out _));
223 | // CMiniMdBase.m_TableDefs
224 | }
225 |
226 | static void ScanHeapOffsets(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint[] heapAddressOffsets, out uint[] heapSizeOffsets) {
227 | const bool InMemory = false;
228 |
229 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
230 | if (uncompressed)
231 | assemblyFlags |= TestAssemblyFlags.Uncompressed;
232 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
233 | nuint module = assembly.ModuleHandle;
234 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
235 | // Get native Module object
236 |
237 | nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module);
238 | uint start = uncompressed ? (sizeof(nuint) == 4 ? 0xD00u : 0x1500) : (sizeof(nuint) == 4 ? 0x2A0u : 0x500);
239 | uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x1000u : 0x1900) : (sizeof(nuint) == 4 ? 0x3A0u : 0x600);
240 | heapAddressOffsets = new uint[4];
241 | heapSizeOffsets = new uint[heapAddressOffsets.Length];
242 | int found = 0;
243 | for (uint offset = start; offset < end; offset += 4) {
244 | nuint address = *(nuint*)(pStgdb + offset);
245 | uint size = *(uint*)(pStgdb + offset + (2 * (uint)sizeof(nuint)));
246 | if (address == info.StringHeapAddress) {
247 | Utils.Check(info.StringHeapSize - 8 < size && size <= info.StringHeapSize);
248 | Utils.Check(heapAddressOffsets[0] == 0);
249 | heapAddressOffsets[StringHeapIndex] = offset;
250 | heapSizeOffsets[StringHeapIndex] = offset + (2 * (uint)sizeof(nuint));
251 | found++;
252 | }
253 | else if (address == info.UserStringHeapAddress) {
254 | Utils.Check(info.UserStringHeapSize - 8 < size && size <= info.UserStringHeapSize);
255 | Utils.Check(heapAddressOffsets[1] == 0);
256 | heapAddressOffsets[UserStringsHeapIndex] = offset;
257 | heapSizeOffsets[UserStringsHeapIndex] = offset + (2 * (uint)sizeof(nuint));
258 | found++;
259 | }
260 | else if (address == info.GuidHeapAddress) {
261 | Utils.Check(info.GuidHeapSize - 8 < size && size <= info.GuidHeapSize);
262 | Utils.Check(heapAddressOffsets[2] == 0);
263 | heapAddressOffsets[GuidHeapIndex] = offset;
264 | heapSizeOffsets[GuidHeapIndex] = offset + (2 * (uint)sizeof(nuint));
265 | found++;
266 | }
267 | else if (address == info.BlobHeapAddress) {
268 | Utils.Check(info.BlobHeapSize - 8 < size && size <= info.BlobHeapSize);
269 | Utils.Check(heapAddressOffsets[3] == 0);
270 | heapAddressOffsets[BlobHeapIndex] = offset;
271 | heapSizeOffsets[BlobHeapIndex] = offset + (2 * (uint)sizeof(nuint));
272 | found++;
273 | }
274 | }
275 | Utils.Check(found == 4);
276 | // Find heeap info offsets
277 |
278 | for (int i = 0; i < heapAddressOffsets.Length; i++)
279 | Utils.Check(Utils.Verify(Utils.WithOffset(stgdbPointer, heapAddressOffsets[i]), uncompressed, p => Memory.TryReadUInt32(p, out _)));
280 | }
281 |
282 | static void ScanTableOffset(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint tableAddressOffset, out uint nextTableOffset) {
283 | const bool InMemory = false;
284 |
285 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0;
286 | if (uncompressed)
287 | assemblyFlags |= TestAssemblyFlags.Uncompressed;
288 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags);
289 | nuint module = assembly.ModuleHandle;
290 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name);
291 | // Get native Module object
292 |
293 | tableAddressOffset = 0;
294 | nextTableOffset = 0;
295 | nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module);
296 | uint start = uncompressed ? (sizeof(nuint) == 4 ? 0x2A0u : 0x500) : (sizeof(nuint) == 4 ? 0x200u : 0x350);
297 | uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x4A0u : 0x800) : (sizeof(nuint) == 4 ? 0x300u : 0x450);
298 | for (uint offset = start; offset < end; offset += 4) {
299 | nuint pFirst = pStgdb + offset;
300 | if (*(nuint*)pFirst != info.TableAddress[0])
301 | continue;
302 |
303 | uint start2 = 4;
304 | uint end2 = uncompressed ? 0x100u : 0x20;
305 | uint offset2 = start2;
306 | for (; offset2 < end2; offset2 += 4) {
307 | if (*(nuint*)(pFirst + offset2) != info.TableAddress[1])
308 | continue;
309 | if (*(nuint*)(pFirst + (2 * offset2)) != info.TableAddress[2])
310 | continue;
311 | break;
312 | }
313 | if (offset2 == end2)
314 | continue;
315 |
316 | tableAddressOffset = offset;
317 | nextTableOffset = offset2;
318 | break;
319 | }
320 | Utils.Check(tableAddressOffset != 0);
321 | Utils.Check(nextTableOffset != 0);
322 | // CMiniMd.m_Tables
323 | }
324 |
325 | public static MetadataInfo GetMetadataInfo(SR.Module module) {
326 | if (module is null)
327 | throw new ArgumentNullException(nameof(module));
328 |
329 | Initialize();
330 | var profile = FindProfile(module);
331 | if (profile is null)
332 | return new MetadataInfo();
333 |
334 | nuint moduleHandle = ReflectionHelpers.GetModuleHandle(module);
335 | var schema = GetSchema(profile, moduleHandle);
336 | var metadataInfo = new MetadataInfo {
337 | MetadataAddress = Utils.ReadUIntPtr(profile.MetadataAddress, moduleHandle),
338 | MetadataSize = Utils.ReadUInt32(profile.MetadataSize, moduleHandle),
339 | Schema = schema,
340 | TableStream = GetTableStream(profile, moduleHandle, schema),
341 | StringHeap = GetHeapInfo(profile, StringHeapIndex, moduleHandle),
342 | UserStringHeap = GetHeapInfo(profile, UserStringsHeapIndex, moduleHandle),
343 | GuidHeap = GetHeapInfo(profile, GuidHeapIndex, moduleHandle),
344 | BlobHeap = GetHeapInfo(profile, BlobHeapIndex, moduleHandle)
345 | };
346 | return metadataInfo;
347 | }
348 |
349 | static Profile? FindProfile(SR.Module module) {
350 | nuint vfptr = MetadataImport.Create(module).Vfptr;
351 | foreach (var profile in profiles) {
352 | if (vfptr == profile.Vfptr)
353 | return profile;
354 | }
355 | Debug2.Assert(false);
356 | return null;
357 | }
358 |
359 | static MetadataSchema GetSchema(Profile profile, nuint moduleHandle) {
360 | var pSchema = (CMiniMdSchema*)Utils.ReadPointer(profile.Schema, moduleHandle);
361 | if (pSchema is null) {
362 | Debug2.Assert(false);
363 | return new();
364 | }
365 | var rows = new uint[TBL_COUNT];
366 | for (int i = 0; i < rows.Length; i++)
367 | rows[i] = pSchema->m_cRecs[i];
368 | var schema = new MetadataSchema {
369 | Reserved1 = pSchema->__base.m_ulReserved,
370 | MajorVersion = pSchema->__base.m_major,
371 | MinorVersion = pSchema->__base.m_minor,
372 | Log2Rid = pSchema->__base.m_rid,
373 | Flags = pSchema->__base.m_heaps,
374 | ValidMask = pSchema->__base.m_maskvalid,
375 | SortedMask = pSchema->__base.m_sorted,
376 | RowCounts = rows,
377 | ExtraData = pSchema->m_ulExtra
378 | };
379 | return schema;
380 | }
381 |
382 | static MetadataTableInfo GetTableStream(Profile profile, nuint moduleHandle, MetadataSchema schema) {
383 | uint tableCount = Utils.ReadUInt32(profile.TableCount, moduleHandle);
384 | var rowSizes = GetRowSizes(profile, moduleHandle);
385 | var rowCounts = schema.RowCounts;
386 | uint tablesSize = 0;
387 | uint validTableCount = 0;
388 | for (int i = 0; i < (int)tableCount; i++) {
389 | if ((schema.ValidMask & (1ul << i)) == 0)
390 | continue;
391 | tablesSize += rowSizes[i] * rowCounts[i];
392 | validTableCount++;
393 | }
394 | uint headerSize = 0x18 + (validTableCount * 4);
395 | nuint pTable = Utils.ReadUIntPtr(profile.TableAddress, moduleHandle);
396 | if (profile.RidAsIndex)
397 | pTable += rowSizes[0];
398 | nuint address = pTable - headerSize;
399 | uint size = headerSize + tablesSize;
400 | var tableInfo = new MetadataTableInfo {
401 | Address = address,
402 | Length = (size + 3) & ~3u,
403 | IsCompressed = !profile.Uncompressed,
404 | TableCount = tableCount,
405 | RowSizes = rowSizes
406 | };
407 | return tableInfo;
408 | }
409 |
410 | static uint[] GetRowSizes(Profile profile, nuint moduleHandle) {
411 | var tableDefs = (CMiniTableDef*)Utils.ReadPointer(profile.TableDefs, moduleHandle);
412 | var rowSizes = new uint[TBL_COUNT];
413 | for (int i = 0; i < TBL_COUNT; i++)
414 | rowSizes[i] = tableDefs[i].m_cbRec;
415 | return rowSizes;
416 | }
417 |
418 | static MetadataHeapInfo GetHeapInfo(Profile profile, int index, nuint moduleHandle) {
419 | uint size = Utils.ReadUInt32(profile.HeapSize[index], moduleHandle);
420 | if (size == 0)
421 | return new MetadataHeapInfo();
422 | // TODO: also check m_pSegData is pointer to m_zeros
423 | var heapInfo = new MetadataHeapInfo {
424 | Address = Utils.ReadUIntPtr(profile.HeapAddress[index], moduleHandle),
425 | Length = (size + 3) & ~3u
426 | };
427 | return heapInfo;
428 | }
429 |
430 | sealed class MiniMetadataInfo {
431 | public ulong Header1;
432 | public ulong ValidMask;
433 | public ulong SortedMask;
434 | public nuint TableStreamAddress;
435 | public uint TableStreamSize;
436 | public nuint StringHeapAddress;
437 | public uint StringHeapSize;
438 | public nuint UserStringHeapAddress;
439 | public uint UserStringHeapSize;
440 | public nuint GuidHeapAddress;
441 | public uint GuidHeapSize;
442 | public nuint BlobHeapAddress;
443 | public uint BlobHeapSize;
444 | public uint[] RowCounts;
445 | public nuint[] TableAddress;
446 |
447 | public MiniMetadataInfo(nuint pMetadata) {
448 | var pStorageSignature = (STORAGESIGNATURE*)pMetadata;
449 | Utils.Check(pStorageSignature);
450 | var pStorageHeader = (STORAGEHEADER*)((nuint)pStorageSignature + 0x10 + pStorageSignature->iVersionString);
451 | nuint p = (nuint)pStorageHeader + (uint)sizeof(STORAGEHEADER);
452 | Utils.Check(pStorageHeader->iStreams == 5);
453 | // must have 5 streams so we can get all stream info offsets
454 | for (int i = 0; i < pStorageHeader->iStreams; i++) {
455 | uint offset = *(uint*)p;
456 | p += 4;
457 | uint size = *(uint*)p;
458 | p += 4;
459 | Utils.Check(Memory.TryReadAnsiString(p, out var name));
460 | p += ((uint)name.Length + 1 + 3) & ~3u;
461 | switch (name) {
462 | case "#~":
463 | case "#-":
464 | Utils.Check(TableStreamAddress == 0);
465 | TableStreamAddress = pMetadata + offset;
466 | TableStreamSize = size;
467 | break;
468 | case "#Strings":
469 | Utils.Check(StringHeapAddress == 0);
470 | StringHeapAddress = pMetadata + offset;
471 | StringHeapSize = size;
472 | break;
473 | case "#US":
474 | Utils.Check(UserStringHeapAddress == 0);
475 | UserStringHeapAddress = pMetadata + offset;
476 | UserStringHeapSize = size;
477 | break;
478 | case "#GUID":
479 | Utils.Check(GuidHeapAddress == 0);
480 | GuidHeapAddress = pMetadata + offset;
481 | GuidHeapSize = size;
482 | break;
483 | case "#Blob":
484 | Utils.Check(BlobHeapAddress == 0);
485 | BlobHeapAddress = pMetadata + offset;
486 | BlobHeapSize = size;
487 | break;
488 | default:
489 | Debug2.Assert(false);
490 | throw new NotSupportedException();
491 | }
492 | }
493 | Header1 = *(ulong*)TableStreamAddress;
494 | ValidMask = *(ulong*)(TableStreamAddress + 0x08);
495 | SortedMask = *(ulong*)(TableStreamAddress + 0x10);
496 | TableAddress = new nuint[TBL_COUNT];
497 | RowCounts = new uint[TBL_COUNT];
498 | p = TableStreamAddress + 0x18;
499 | for (int i = 0; i < TBL_COUNT; i++) {
500 | if ((ValidMask & (1ul << i)) == 0)
501 | continue;
502 | RowCounts[i] = *(uint*)p;
503 | p += 4;
504 | }
505 | TableAddress[0] = p;
506 | }
507 |
508 | public void CalculateTableAddress(nuint tableDefs, bool ridAsIndex) {
509 | nuint p = TableAddress[0];
510 | var tableDefs2 = (CMiniTableDef*)tableDefs;
511 | for (int i = 0; i < TBL_COUNT; i++) {
512 | TableAddress[i] = ridAsIndex ? p - tableDefs2[i].m_cbRec : p;
513 | p += RowCounts[i] * tableDefs2[i].m_cbRec;
514 | }
515 | }
516 | }
517 | }
518 |
--------------------------------------------------------------------------------