├── .gitattributes ├── .gitignore ├── ManagedDotnetGC.sln ├── ManagedDotnetGC ├── Dac │ ├── ClrDataTarget.cs │ ├── DacManager.cs │ ├── ICLRDataTarget.cs │ ├── ICLRDataTarget2.cs │ ├── ISOSDacInterface.cs │ └── Types.cs ├── DllMain.cs ├── GCDesc.cs ├── GCHandleManager.cs ├── GCHandleStore.cs ├── GCHeap.cs ├── HResult.cs ├── Interfaces │ ├── IGCHandleManager.cs │ ├── IGCHandleStore.cs │ ├── IGCHeap.cs │ ├── IGCToCLR.cs │ └── IUnknown.cs ├── Log.cs ├── ManagedDotnetGC.csproj ├── Properties │ └── launchSettings.json └── Types.cs ├── ManagedDotnetGCLoader ├── ManagedDotnetGCLoader.vcxproj ├── ManagedDotnetGCLoader.vcxproj.filters ├── dllmain.cpp ├── framework.h ├── pch.cpp └── pch.h ├── TestApp.sln ├── TestApp ├── Program.cs ├── Properties │ └── launchSettings.json ├── StaticClass.cs ├── TestApp.csproj └── launch.cmd └── publish.cmd /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | /nugets/** 254 | -------------------------------------------------------------------------------- /ManagedDotnetGC.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32519.111 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedDotnetGC", "ManagedDotnetGC\ManagedDotnetGC.csproj", "{EDCE7242-B4EC-4837-9388-6313D6792062}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "TestApp\TestApp.csproj", "{194EBA61-D34A-4E5B-A65E-681A08C1FE9D}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManagedDotnetGCLoader", "ManagedDotnetGCLoader\ManagedDotnetGCLoader.vcxproj", "{B22B0B4B-C3D2-4141-85C1-262AC52218B2}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Debug|x64.ActiveCfg = Debug|Any CPU 25 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Debug|x64.Build.0 = Debug|Any CPU 26 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Debug|x86.ActiveCfg = Debug|Any CPU 27 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Debug|x86.Build.0 = Debug|Any CPU 28 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Release|x64.ActiveCfg = Release|Any CPU 31 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Release|x64.Build.0 = Release|Any CPU 32 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Release|x86.ActiveCfg = Release|Any CPU 33 | {EDCE7242-B4EC-4837-9388-6313D6792062}.Release|x86.Build.0 = Release|Any CPU 34 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Debug|x64.ActiveCfg = Debug|Any CPU 37 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Debug|x64.Build.0 = Debug|Any CPU 38 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Debug|x86.ActiveCfg = Debug|Any CPU 39 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Debug|x86.Build.0 = Debug|Any CPU 40 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Release|x64.ActiveCfg = Release|Any CPU 43 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Release|x64.Build.0 = Release|Any CPU 44 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Release|x86.ActiveCfg = Release|Any CPU 45 | {194EBA61-D34A-4E5B-A65E-681A08C1FE9D}.Release|x86.Build.0 = Release|Any CPU 46 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Debug|Any CPU.ActiveCfg = Debug|x64 47 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Debug|Any CPU.Build.0 = Debug|x64 48 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Debug|x64.ActiveCfg = Debug|x64 49 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Debug|x64.Build.0 = Debug|x64 50 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Debug|x86.ActiveCfg = Debug|Win32 51 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Debug|x86.Build.0 = Debug|Win32 52 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Release|Any CPU.ActiveCfg = Release|x64 53 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Release|Any CPU.Build.0 = Release|x64 54 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Release|x64.ActiveCfg = Release|x64 55 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Release|x64.Build.0 = Release|x64 56 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Release|x86.ActiveCfg = Release|Win32 57 | {B22B0B4B-C3D2-4141-85C1-262AC52218B2}.Release|x86.Build.0 = Release|Win32 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(ExtensibilityGlobals) = postSolution 63 | SolutionGuid = {B32F82C1-583B-4B97-9C54-DACA8E84461A} 64 | EndGlobalSection 65 | EndGlobal 66 | -------------------------------------------------------------------------------- /ManagedDotnetGC/Dac/ClrDataTarget.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace ManagedDotnetGC.Dac; 5 | 6 | public unsafe class ClrDataTarget : ICLRDataTarget2, IDisposable 7 | { 8 | private readonly NativeObjects.ICLRDataTarget _clrDataTarget; 9 | private int _referenceCount; 10 | 11 | public ClrDataTarget() 12 | { 13 | _clrDataTarget = NativeObjects.ICLRDataTarget.Wrap(this); 14 | } 15 | 16 | public IntPtr ICLRDataTargetObject => _clrDataTarget; 17 | 18 | public void Dispose() 19 | { 20 | _clrDataTarget.Dispose(); 21 | } 22 | 23 | public HResult QueryInterface(in Guid guid, out IntPtr ptr) 24 | { 25 | if (guid == ICLRDataTarget2.Guid) 26 | { 27 | ptr = _clrDataTarget; 28 | return HResult.S_OK; 29 | } 30 | 31 | ptr = default; 32 | return HResult.E_NOINTERFACE; 33 | } 34 | 35 | public int AddRef() 36 | { 37 | Console.WriteLine("ClrDataTarget - AddRef"); 38 | return Interlocked.Increment(ref _referenceCount); 39 | } 40 | 41 | public int Release() 42 | { 43 | Console.WriteLine("ClrDataTarget - Release"); 44 | var value = Interlocked.Decrement(ref _referenceCount); 45 | 46 | if (value == 0) 47 | { 48 | Dispose(); 49 | } 50 | 51 | return value; 52 | } 53 | 54 | public HResult GetMachineType(out uint machine) 55 | { 56 | machine = 0x8664; // IMAGE_FILE_MACHINE_AMD64 57 | return HResult.S_OK; 58 | } 59 | 60 | public HResult GetPointerSize(out uint size) 61 | { 62 | size = (uint)IntPtr.Size; 63 | return HResult.S_OK; 64 | } 65 | 66 | public HResult GetImageBase(char* moduleName, out CLRDATA_ADDRESS baseAddress) 67 | { 68 | var name = new string(moduleName); 69 | 70 | foreach (ProcessModule module in Process.GetCurrentProcess().Modules) 71 | { 72 | if (module.ModuleName == name) 73 | { 74 | baseAddress = new CLRDATA_ADDRESS(module.BaseAddress.ToInt64()); 75 | return HResult.S_OK; 76 | } 77 | } 78 | 79 | baseAddress = default; 80 | return HResult.E_FAIL; 81 | } 82 | 83 | public HResult ReadVirtual(CLRDATA_ADDRESS address, byte* buffer, uint size, out uint done) 84 | { 85 | Unsafe.CopyBlock(buffer, (void*)(IntPtr)address.Value, size); 86 | done = size; 87 | return HResult.S_OK; 88 | } 89 | 90 | public HResult WriteVirtual(CLRDATA_ADDRESS address, byte* buffer, uint size, out uint done) 91 | { 92 | done = default; 93 | return HResult.E_NOTIMPL; 94 | } 95 | 96 | public HResult GetTLSValue(uint threadID, uint index, out CLRDATA_ADDRESS value) 97 | { 98 | value = default; 99 | return HResult.E_NOTIMPL; 100 | } 101 | 102 | public HResult SetTLSValue(uint threadID, uint index, CLRDATA_ADDRESS value) 103 | { 104 | return HResult.E_NOTIMPL; 105 | } 106 | 107 | public HResult GetCurrentThreadID(out uint threadID) 108 | { 109 | threadID = default; 110 | return HResult.E_NOTIMPL; 111 | } 112 | 113 | public HResult GetThreadContext(uint threadID, uint contextFlags, uint contextSize, byte* context) 114 | { 115 | return HResult.E_NOTIMPL; 116 | } 117 | 118 | public HResult SetThreadContext(uint threadID, uint contextSize, byte* context) 119 | { 120 | return HResult.E_NOTIMPL; 121 | } 122 | 123 | public HResult Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) 124 | { 125 | return HResult.E_NOTIMPL; 126 | } 127 | 128 | public HResult AllocVirtual(CLRDATA_ADDRESS addr, uint size, uint typeFlags, uint protectFlags, out CLRDATA_ADDRESS virt) 129 | { 130 | virt = default; 131 | return HResult.E_NOTIMPL; 132 | } 133 | 134 | public HResult FreeVirtual(CLRDATA_ADDRESS addr, uint size, uint typeFlags) 135 | { 136 | return HResult.E_NOTIMPL; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /ManagedDotnetGC/Dac/DacManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ManagedDotnetGC.Dac; 5 | 6 | public class DacManager : IDisposable 7 | { 8 | private static readonly Guid IClrDataProcessGuid = new("5c552ab6-fc09-4cb3-8e36-22fa03c798b7"); 9 | 10 | private IntPtr _libraryHandle; 11 | 12 | private DacManager(IntPtr libraryHandle, IntPtr dac) 13 | { 14 | _libraryHandle = libraryHandle; 15 | Dac = NativeObjects.ISOSDacInterface.Wrap(dac); 16 | } 17 | 18 | public NativeObjects.ISOSDacInterfaceInvoker Dac { get; private set; } 19 | 20 | public static unsafe HResult TryLoad(out DacManager? dacManager) 21 | { 22 | var module = Process.GetCurrentProcess().Modules 23 | .Cast() 24 | .FirstOrDefault(m => m.ModuleName == "coreclr.dll"); 25 | 26 | if (module == null) 27 | { 28 | Log.Write("coreclr.dll not found"); 29 | dacManager = null; 30 | return HResult.E_FAIL; 31 | } 32 | 33 | var dacPath = Path.Combine(Path.GetDirectoryName(module.FileName)!, "mscordaccore.dll"); 34 | 35 | if (!File.Exists(dacPath)) 36 | { 37 | Log.Write($"The DAC wasn't found at the expected path ({dacPath})"); 38 | dacManager = null; 39 | return HResult.E_FAIL; 40 | } 41 | 42 | var library = NativeLibrary.Load(dacPath); 43 | 44 | try 45 | { 46 | var export = NativeLibrary.GetExport(library, "CLRDataCreateInstance"); 47 | var createInstance = (delegate* unmanaged[Stdcall])export; 48 | 49 | var dataTarget = new ClrDataTarget(); 50 | var result = createInstance(IClrDataProcessGuid, dataTarget.ICLRDataTargetObject, out var pUnk); 51 | 52 | var unknown = NativeObjects.IUnknown.Wrap(pUnk); 53 | result = unknown.QueryInterface(ISOSDacInterface.Guid, out var sosDacInterfacePtr); 54 | 55 | dacManager = result ? new DacManager(library, sosDacInterfacePtr) : null; 56 | return result; 57 | } 58 | catch 59 | { 60 | NativeLibrary.Free(library); 61 | throw; 62 | } 63 | } 64 | 65 | public unsafe string? GetObjectName(CLRDATA_ADDRESS address) 66 | { 67 | var result = Dac.GetObjectClassName(address, 0, null, out var needed); 68 | 69 | if (!result) 70 | { 71 | return null; 72 | } 73 | 74 | Span str = stackalloc char[(int)needed]; 75 | 76 | fixed (char* p = str) 77 | { 78 | result = Dac.GetObjectClassName(address, needed, p, out _); 79 | 80 | if (!result) 81 | { 82 | return null; 83 | } 84 | 85 | return new string(str); 86 | } 87 | } 88 | 89 | public void Dispose() 90 | { 91 | Dac.Release(); 92 | NativeLibrary.Free(_libraryHandle); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ManagedDotnetGC/Dac/ICLRDataTarget.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Dac; 2 | 3 | [NativeObject] 4 | public unsafe interface ICLRDataTarget : Interfaces.IUnknown 5 | { 6 | HResult GetMachineType(out uint machine); 7 | 8 | HResult GetPointerSize(out uint size); 9 | 10 | HResult GetImageBase(char* moduleName, out CLRDATA_ADDRESS baseAddress); 11 | 12 | HResult ReadVirtual( 13 | CLRDATA_ADDRESS address, 14 | byte* buffer, 15 | uint size, 16 | out uint done); 17 | 18 | HResult WriteVirtual( 19 | CLRDATA_ADDRESS address, 20 | byte* buffer, 21 | uint size, 22 | out uint done); 23 | 24 | HResult GetTLSValue( 25 | uint threadID, 26 | uint index, 27 | out CLRDATA_ADDRESS value); 28 | 29 | HResult SetTLSValue( 30 | uint threadID, 31 | uint index, 32 | CLRDATA_ADDRESS value); 33 | 34 | HResult GetCurrentThreadID( 35 | out uint threadID); 36 | 37 | HResult GetThreadContext( 38 | uint threadID, 39 | uint contextFlags, 40 | uint contextSize, 41 | byte* context); 42 | 43 | HResult SetThreadContext( 44 | uint threadID, 45 | uint contextSize, 46 | byte* context); 47 | 48 | HResult Request( 49 | uint reqCode, 50 | uint inBufferSize, 51 | byte* inBuffer, 52 | uint outBufferSize, 53 | byte* outBuffer); 54 | } 55 | -------------------------------------------------------------------------------- /ManagedDotnetGC/Dac/ICLRDataTarget2.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Dac; 2 | 3 | [NativeObject] 4 | public interface ICLRDataTarget2 : ICLRDataTarget 5 | { 6 | public static readonly Guid Guid = new("6d05fae3-189c-4630-a6dc-1c251e1c01ab"); 7 | 8 | HResult AllocVirtual( 9 | CLRDATA_ADDRESS addr, 10 | uint size, 11 | uint typeFlags, 12 | uint protectFlags, 13 | out CLRDATA_ADDRESS virt); 14 | 15 | HResult FreeVirtual( 16 | CLRDATA_ADDRESS addr, 17 | uint size, 18 | uint typeFlags); 19 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/Dac/ISOSDacInterface.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Dac; 2 | 3 | using ULONG = UInt64; 4 | using WCHAR = Char; 5 | using DWORD = UInt32; 6 | 7 | [NativeObject] 8 | public unsafe interface ISOSDacInterface : Interfaces.IUnknown 9 | { 10 | public static readonly Guid Guid = new("436f00f2-b42a-4b9f-870c-e73db66ae930"); 11 | 12 | HResult GetThreadStoreData(out DacpThreadStoreData data); 13 | 14 | HResult GetAppDomainStoreData(out DacpAppDomainStoreData data); 15 | 16 | HResult GetAppDomainList( 17 | uint count, 18 | CLRDATA_ADDRESS* values, 19 | out uint pNeeded); 20 | 21 | HResult GetAppDomainData( 22 | CLRDATA_ADDRESS addr, 23 | out DacpAppDomainData data); 24 | 25 | HResult GetAppDomainName( 26 | CLRDATA_ADDRESS addr, 27 | uint count, 28 | char* name, 29 | out uint pNeeded); 30 | 31 | HResult GetDomainFromContext( 32 | CLRDATA_ADDRESS context, 33 | out CLRDATA_ADDRESS domain); 34 | 35 | HResult GetAssemblyList( 36 | CLRDATA_ADDRESS appDomain, 37 | int count, 38 | CLRDATA_ADDRESS* values, 39 | out int pNeeded); 40 | 41 | HResult GetAssemblyData( 42 | CLRDATA_ADDRESS baseDomainPtr, 43 | CLRDATA_ADDRESS assembly, 44 | out DacpAssemblyData data); 45 | 46 | HResult GetAssemblyName( 47 | CLRDATA_ADDRESS assembly, 48 | uint count, 49 | char* name, 50 | out uint pNeeded); 51 | 52 | HResult GetModule( 53 | CLRDATA_ADDRESS addr, 54 | out IntPtr mod); 55 | 56 | HResult GetModuleData( 57 | CLRDATA_ADDRESS moduleAddr, 58 | out DacpModuleData data); 59 | 60 | HResult TraverseModuleMap( 61 | ModuleMapType mmt, 62 | CLRDATA_ADDRESS moduleAddr, 63 | delegate* unmanaged[Stdcall] pCallback, 64 | IntPtr token); 65 | 66 | HResult GetAssemblyModuleList( 67 | CLRDATA_ADDRESS assembly, 68 | uint count, 69 | CLRDATA_ADDRESS* modules, 70 | out uint pNeeded); 71 | 72 | HResult GetILForModule( 73 | CLRDATA_ADDRESS moduleAddr, 74 | int rva, 75 | out CLRDATA_ADDRESS il); 76 | 77 | HResult GetThreadData( 78 | CLRDATA_ADDRESS thread, 79 | out DacpThreadData data); 80 | 81 | HResult GetThreadFromThinlockID( 82 | uint thinLockId, 83 | CLRDATA_ADDRESS* pThread); 84 | 85 | HResult GetStackLimits( 86 | CLRDATA_ADDRESS threadPtr, 87 | CLRDATA_ADDRESS* lower, 88 | CLRDATA_ADDRESS* upper, 89 | CLRDATA_ADDRESS* fp); 90 | 91 | HResult GetMethodDescData( 92 | CLRDATA_ADDRESS methodDesc, 93 | CLRDATA_ADDRESS ip, 94 | DacpMethodDescData* data, 95 | ULONG cRevertedRejitVersions, 96 | DacpReJitData* rgRevertedRejitData, 97 | ULONG* pcNeededRevertedRejitData); 98 | 99 | HResult GetMethodDescPtrFromIP( 100 | CLRDATA_ADDRESS ip, 101 | CLRDATA_ADDRESS* ppMD); 102 | 103 | HResult GetMethodDescName( 104 | CLRDATA_ADDRESS methodDesc, 105 | uint count, 106 | WCHAR* name, 107 | uint* pNeeded); 108 | 109 | HResult GetMethodDescPtrFromFrame( 110 | CLRDATA_ADDRESS frameAddr, 111 | CLRDATA_ADDRESS* ppMD); 112 | 113 | HResult GetMethodDescFromToken( 114 | CLRDATA_ADDRESS moduleAddr, 115 | MdToken token, 116 | CLRDATA_ADDRESS* methodDesc); 117 | 118 | HResult GetMethodDescTransparencyData( 119 | CLRDATA_ADDRESS methodDesc, 120 | out DacpMethodDescTransparencyData data); 121 | 122 | HResult GetCodeHeaderData( 123 | CLRDATA_ADDRESS ip, 124 | out DacpCodeHeaderData data); 125 | 126 | HResult GetJitManagerList( 127 | uint count, 128 | DacpJitManagerInfo* managers, 129 | out uint pNeeded); 130 | 131 | HResult GetJitHelperFunctionName( 132 | CLRDATA_ADDRESS ip, 133 | uint count, 134 | char* name, 135 | out uint pNeeded); 136 | 137 | HResult GetJumpThunkTarget( 138 | T_CONTEXT* ctx, 139 | out CLRDATA_ADDRESS targetIP, 140 | out CLRDATA_ADDRESS targetMD); 141 | 142 | HResult GetThreadpoolData( 143 | out DacpThreadpoolData data); 144 | 145 | HResult GetWorkRequestData( 146 | CLRDATA_ADDRESS addrWorkRequest, 147 | out DacpWorkRequestData data); 148 | 149 | HResult GetHillClimbingLogEntry( 150 | CLRDATA_ADDRESS addr, 151 | out DacpHillClimbingLogEntry data); 152 | 153 | HResult GetObjectData( 154 | CLRDATA_ADDRESS objAddr, 155 | out DacpObjectData data); 156 | 157 | HResult GetObjectStringData( 158 | CLRDATA_ADDRESS obj, 159 | uint count, 160 | WCHAR* stringData, 161 | out uint pNeeded); 162 | 163 | HResult GetObjectClassName( 164 | CLRDATA_ADDRESS obj, 165 | uint count, 166 | WCHAR* className, 167 | out uint pNeeded); 168 | 169 | HResult GetMethodTableName( 170 | CLRDATA_ADDRESS mt, 171 | uint count, 172 | WCHAR* mtName, 173 | out uint pNeeded); 174 | 175 | HResult GetMethodTableData( 176 | CLRDATA_ADDRESS mt, 177 | out DacpMethodTableData data); 178 | 179 | HResult GetMethodTableSlot( 180 | CLRDATA_ADDRESS mt, 181 | uint slot, 182 | out CLRDATA_ADDRESS value); 183 | 184 | HResult GetMethodTableFieldData( 185 | CLRDATA_ADDRESS mt, 186 | out DacpMethodTableFieldData data); 187 | 188 | HResult GetMethodTableTransparencyData( 189 | CLRDATA_ADDRESS mt, 190 | out DacpMethodTableTransparencyData data); 191 | 192 | HResult GetMethodTableForEEClass( 193 | CLRDATA_ADDRESS eeClass, 194 | out CLRDATA_ADDRESS value); 195 | 196 | HResult GetFieldDescData( 197 | CLRDATA_ADDRESS fieldDesc, 198 | out DacpFieldDescData data); 199 | 200 | HResult GetFrameName( 201 | CLRDATA_ADDRESS vtable, 202 | uint count, 203 | WCHAR* frameName, 204 | out uint pNeeded); 205 | 206 | HResult GetPEFileBase( 207 | CLRDATA_ADDRESS addr, 208 | out CLRDATA_ADDRESS baseAddress); 209 | 210 | HResult GetPEFileName( 211 | CLRDATA_ADDRESS addr, 212 | uint count, 213 | WCHAR* fileName, 214 | out uint pNeeded); 215 | 216 | HResult GetGCHeapData( 217 | out DacpGcHeapData data); 218 | 219 | HResult GetGCHeapList( 220 | uint count, 221 | CLRDATA_ADDRESS* heaps, 222 | out uint pNeeded); 223 | 224 | HResult GetGCHeapDetails( 225 | CLRDATA_ADDRESS heap, 226 | out DacpGcHeapDetails details); 227 | 228 | HResult GetGCHeapStaticData( 229 | out DacpGcHeapDetails data); 230 | 231 | HResult GetHeapSegmentData( 232 | CLRDATA_ADDRESS seg, 233 | out DacpHeapSegmentData data); 234 | 235 | HResult GetOOMData( 236 | CLRDATA_ADDRESS oomAddr, 237 | out DacpOomData data); 238 | 239 | HResult GetOOMStaticData( 240 | out DacpOomData data); 241 | 242 | HResult GetHeapAnalyzeData( 243 | CLRDATA_ADDRESS addr, 244 | out DacpGcHeapAnalyzeData data); 245 | 246 | HResult GetHeapAnalyzeStaticData( 247 | out DacpGcHeapAnalyzeData data); 248 | 249 | HResult GetDomainLocalModuleData( 250 | CLRDATA_ADDRESS addr, 251 | out DacpDomainLocalModuleData data); 252 | 253 | HResult GetDomainLocalModuleDataFromAppDomain( 254 | CLRDATA_ADDRESS appDomainAddr, 255 | int moduleID, 256 | out DacpDomainLocalModuleData data); 257 | 258 | HResult GetDomainLocalModuleDataFromModule( 259 | CLRDATA_ADDRESS moduleAddr, 260 | out DacpDomainLocalModuleData data); 261 | 262 | HResult GetThreadLocalModuleData( 263 | CLRDATA_ADDRESS thread, 264 | uint index, 265 | out DacpThreadLocalModuleData data); 266 | 267 | HResult GetSyncBlockData( 268 | uint number, 269 | out DacpSyncBlockData data); 270 | 271 | HResult GetSyncBlockCleanupData( 272 | CLRDATA_ADDRESS addr, 273 | out DacpSyncBlockCleanupData data); 274 | 275 | HResult GetHandleEnum( 276 | out IntPtr ppHandleEnum); 277 | 278 | HResult GetHandleEnumForTypes( 279 | uint* types, 280 | uint count, 281 | out IntPtr ppHandleEnum); 282 | 283 | HResult GetHandleEnumForGC( 284 | uint gen, 285 | out IntPtr ppHandleEnum); 286 | 287 | HResult TraverseEHInfo( 288 | CLRDATA_ADDRESS ip, 289 | void* pCallback, 290 | IntPtr token); 291 | 292 | HResult GetNestedExceptionData( 293 | CLRDATA_ADDRESS exception, 294 | out CLRDATA_ADDRESS exceptionObject, 295 | out CLRDATA_ADDRESS nextNestedException); 296 | 297 | HResult GetStressLogAddress( 298 | out CLRDATA_ADDRESS stressLog); 299 | 300 | HResult TraverseLoaderHeap( 301 | CLRDATA_ADDRESS loaderHeapAddr, 302 | delegate* unmanaged[Stdcall] pCallback); 303 | 304 | HResult GetCodeHeapList( 305 | CLRDATA_ADDRESS jitManager, 306 | uint count, 307 | void* codeHeaps, 308 | out uint pNeeded); 309 | 310 | HResult TraverseVirtCallStubHeap( 311 | CLRDATA_ADDRESS pAppDomain, 312 | VCSHeapType heaptype, 313 | delegate* unmanaged[Stdcall] pCallback); 314 | 315 | HResult GetUsefulGlobals( 316 | out DacpUsefulGlobalsData data); 317 | 318 | HResult GetClrWatsonBuckets( 319 | CLRDATA_ADDRESS thread, 320 | void* pGenericModeBlock); 321 | 322 | HResult GetTLSIndex( 323 | out ULONG pIndex); 324 | 325 | HResult GetDacModuleHandle( 326 | out HMODULE phModule); 327 | 328 | HResult GetRCWData( 329 | CLRDATA_ADDRESS addr, 330 | out DacpRCWData data); 331 | 332 | HResult GetRCWInterfaces( 333 | CLRDATA_ADDRESS rcw, 334 | uint count, 335 | out DacpCOMInterfacePointerData interfaces, 336 | out uint pNeeded); 337 | 338 | HResult GetCCWData( 339 | CLRDATA_ADDRESS ccw, 340 | out DacpCCWData data); 341 | 342 | HResult GetCCWInterfaces( 343 | CLRDATA_ADDRESS ccw, 344 | uint count, 345 | DacpCOMInterfacePointerData* interfaces, 346 | out uint pNeeded); 347 | 348 | HResult TraverseRCWCleanupList( 349 | CLRDATA_ADDRESS cleanupListPtr, 350 | void* pCallback, 351 | IntPtr token); 352 | 353 | HResult GetStackReferences( 354 | /* [in] */ DWORD osThreadID, 355 | /* [out] */ out IntPtr ppEnum); 356 | 357 | HResult GetRegisterName( 358 | /* [in] */ int regName, 359 | /* [in] */ uint count, 360 | /* [out] */ WCHAR* buffer, 361 | /* [out] */ out uint pNeeded); 362 | 363 | HResult GetThreadAllocData( 364 | CLRDATA_ADDRESS thread, 365 | out DacpAllocData data); 366 | 367 | HResult GetHeapAllocData( 368 | uint count, 369 | DacpGenerationAllocData* data, 370 | out uint pNeeded); 371 | 372 | HResult GetFailedAssemblyList( 373 | CLRDATA_ADDRESS appDomain, 374 | int count, 375 | CLRDATA_ADDRESS* values, 376 | out uint pNeeded); 377 | 378 | HResult GetPrivateBinPaths( 379 | CLRDATA_ADDRESS appDomain, 380 | int count, 381 | WCHAR* paths, 382 | out uint pNeeded); 383 | 384 | HResult GetAssemblyLocation( 385 | CLRDATA_ADDRESS assembly, 386 | int count, 387 | WCHAR* location, 388 | out uint pNeeded); 389 | 390 | HResult GetAppDomainConfigFile( 391 | CLRDATA_ADDRESS appDomain, 392 | int count, 393 | WCHAR* configFile, 394 | out uint pNeeded); 395 | 396 | HResult GetApplicationBase( 397 | CLRDATA_ADDRESS appDomain, 398 | int count, 399 | WCHAR* applicationBase, 400 | out uint pNeeded); 401 | 402 | HResult GetFailedAssemblyData( 403 | CLRDATA_ADDRESS assembly, 404 | out uint pContext, 405 | out HResult pResult); 406 | 407 | HResult GetFailedAssemblyLocation( 408 | CLRDATA_ADDRESS assembly, 409 | uint count, 410 | WCHAR* location, 411 | out uint pNeeded); 412 | 413 | HResult GetFailedAssemblyDisplayName( 414 | CLRDATA_ADDRESS assembly, 415 | uint count, 416 | WCHAR* name, 417 | out uint pNeeded); 418 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/Dac/Types.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Dac; 2 | 3 | using System.Diagnostics; 4 | 5 | using ULONG64 = UInt64; 6 | using ULONG = UInt32; 7 | using LONG = Int32; 8 | using UINT = UInt32; 9 | using BOOL = Int32; 10 | using DWORD = UInt32; 11 | using WORD = Int16; 12 | 13 | public readonly struct GcDacVars 14 | { 15 | public readonly byte Major_version_number; 16 | public readonly byte Minor_version_number; 17 | public readonly nint Generation_size; 18 | public readonly nint Total_generation_count; 19 | } 20 | 21 | public unsafe struct VersionInfo 22 | { 23 | public int MajorVersion; 24 | public int MinorVersion; 25 | public int BuildVersion; 26 | public char* Name; 27 | } 28 | 29 | // SUSPEND_REASON is the reason why the GC wishes to suspend the EE, 30 | // used as an argument to IGCToCLR::SuspendEE. 31 | public enum SUSPEND_REASON 32 | { 33 | SUSPEND_FOR_GC = 1, 34 | SUSPEND_FOR_GC_PREP = 6 35 | } 36 | 37 | public readonly struct GCObject 38 | { 39 | public readonly IntPtr MethodTable; 40 | public readonly int Length; 41 | } 42 | 43 | public unsafe struct gc_alloc_context 44 | { 45 | public nint alloc_ptr; 46 | public nint alloc_limit; 47 | public long alloc_bytes; //Number of bytes allocated on SOH by this context 48 | 49 | public long alloc_bytes_uoh; //Number of bytes allocated not on SOH by this context 50 | 51 | // These two fields are deliberately not exposed past the EE-GC interface. 52 | public void* gc_reserved_1; 53 | public void* gc_reserved_2; 54 | public int alloc_count; 55 | } 56 | 57 | public enum walk_surv_type 58 | { 59 | walk_for_gc = 1, 60 | walk_for_bgc = 2, 61 | walk_for_uoh = 3 62 | } 63 | 64 | public unsafe struct segment_info 65 | { 66 | public void* pvMem; // base of the allocation, not the first object (must add ibFirstObject) 67 | public nint ibFirstObject; // offset to the base of the first object in the segment 68 | public nint ibAllocated; // limit of allocated memory in the segment (>= firstobject) 69 | public nint ibCommit; // limit of committed memory in the segment (>= allocated) 70 | public nint ibReserved; // limit of reserved memory in the segment (>= commit) 71 | } 72 | 73 | // Event keywords corresponding to events that can be fired by the GC. These 74 | // numbers come from the ETW manifest itself - please make changes to this enum 75 | // if you add, remove, or change keyword sets that are used by the GC! 76 | [Flags] 77 | public enum GCEventKeyword 78 | { 79 | GCEventKeyword_None = 0x0, 80 | GCEventKeyword_GC = 0x1, 81 | // Duplicate on purpose, GCPrivate is the same keyword as GC, 82 | // with a different provider 83 | GCEventKeyword_GCPrivate = 0x1, 84 | GCEventKeyword_GCHandle = 0x2, 85 | GCEventKeyword_GCHandlePrivate = 0x4000, 86 | GCEventKeyword_GCHeapDump = 0x100000, 87 | GCEventKeyword_GCSampledObjectAllocationHigh = 0x200000, 88 | GCEventKeyword_GCHeapSurvivalAndMovement = 0x400000, 89 | GCEventKeyword_GCHeapCollect = 0x800000, 90 | GCEventKeyword_GCHeapAndTypeNames = 0x1000000, 91 | GCEventKeyword_GCSampledObjectAllocationLow = 0x2000000, 92 | GCEventKeyword_All = GCEventKeyword_GC 93 | | GCEventKeyword_GCPrivate 94 | | GCEventKeyword_GCHandle 95 | | GCEventKeyword_GCHandlePrivate 96 | | GCEventKeyword_GCHeapDump 97 | | GCEventKeyword_GCSampledObjectAllocationHigh 98 | | GCEventKeyword_GCHeapSurvivalAndMovement 99 | | GCEventKeyword_GCHeapCollect 100 | | GCEventKeyword_GCHeapAndTypeNames 101 | | GCEventKeyword_GCSampledObjectAllocationLow 102 | } 103 | 104 | // Event levels corresponding to events that can be fired by the GC. 105 | public enum GCEventLevel 106 | { 107 | GCEventLevel_None = 0, 108 | GCEventLevel_Fatal = 1, 109 | GCEventLevel_Error = 2, 110 | GCEventLevel_Warning = 3, 111 | GCEventLevel_Information = 4, 112 | GCEventLevel_Verbose = 5, 113 | GCEventLevel_Max = 6, 114 | GCEventLevel_LogAlways = 255 115 | } 116 | 117 | public unsafe struct OBJECTHANDLE 118 | { 119 | public OBJECTHANDLE(nint value) 120 | { 121 | Value = value; 122 | } 123 | 124 | public nint Value; 125 | } 126 | 127 | public enum HandleType 128 | { 129 | /* 130 | * WEAK HANDLES 131 | * 132 | * Weak handles are handles that track an object as long as it is alive, 133 | * but do not keep the object alive if there are no strong references to it. 134 | * 135 | */ 136 | 137 | /* 138 | * SHORT-LIVED WEAK HANDLES 139 | * 140 | * Short-lived weak handles are weak handles that track an object until the 141 | * first time it is detected to be unreachable. At this point, the handle is 142 | * severed, even if the object will be visible from a pending finalization 143 | * graph. This further implies that short weak handles do not track 144 | * across object resurrections. 145 | * 146 | */ 147 | HNDTYPE_WEAK_SHORT = 0, 148 | 149 | /* 150 | * LONG-LIVED WEAK HANDLES 151 | * 152 | * Long-lived weak handles are weak handles that track an object until the 153 | * object is actually reclaimed. Unlike short weak handles, long weak handles 154 | * continue to track their referents through finalization and across any 155 | * resurrections that may occur. 156 | * 157 | */ 158 | HNDTYPE_WEAK_LONG = 1, 159 | HNDTYPE_WEAK_DEFAULT = 1, 160 | 161 | /* 162 | * STRONG HANDLES 163 | * 164 | * Strong handles are handles which function like a normal object reference. 165 | * The existence of a strong handle for an object will cause the object to 166 | * be promoted (remain alive) through a garbage collection cycle. 167 | * 168 | */ 169 | HNDTYPE_STRONG = 2, 170 | HNDTYPE_DEFAULT = 2, 171 | 172 | /* 173 | * PINNED HANDLES 174 | * 175 | * Pinned handles are strong handles which have the added property that they 176 | * prevent an object from moving during a garbage collection cycle. This is 177 | * useful when passing a pointer to object innards out of the runtime while GC 178 | * may be enabled. 179 | * 180 | * NOTE: PINNING AN OBJECT IS EXPENSIVE AS IT PREVENTS THE GC FROM ACHIEVING 181 | * OPTIMAL PACKING OF OBJECTS DURING EPHEMERAL COLLECTIONS. THIS TYPE 182 | * OF HANDLE SHOULD BE USED SPARINGLY! 183 | */ 184 | HNDTYPE_PINNED = 3, 185 | 186 | /* 187 | * VARIABLE HANDLES 188 | * 189 | * Variable handles are handles whose type can be changed dynamically. They 190 | * are larger than other types of handles, and are scanned a little more often, 191 | * but are useful when the handle owner needs an efficient way to change the 192 | * strength of a handle on the fly. 193 | * 194 | */ 195 | HNDTYPE_VARIABLE = 4, 196 | 197 | /* 198 | * REFCOUNTED HANDLES 199 | * 200 | * Refcounted handles are handles that behave as strong handles while the 201 | * refcount on them is greater than 0 and behave as weak handles otherwise. 202 | * 203 | * N.B. These are currently NOT general purpose. 204 | * The implementation is tied to COM Interop. 205 | * 206 | */ 207 | HNDTYPE_REFCOUNTED = 5, 208 | 209 | /* 210 | * DEPENDENT HANDLES 211 | * 212 | * Dependent handles are two handles that need to have the same lifetime. One handle refers to a secondary object 213 | * that needs to have the same lifetime as the primary object. The secondary object should not cause the primary 214 | * object to be referenced, but as long as the primary object is alive, so must be the secondary 215 | * 216 | * They are currently used for EnC for adding new field members to existing instantiations under EnC modes where 217 | * the primary object is the original instantiation and the secondary represents the added field. 218 | * 219 | * They are also used to implement the managed ConditionalWeakTable class. If you want to use 220 | * these from managed code, they are exposed to BCL through the managed DependentHandle class. 221 | * 222 | * 223 | */ 224 | HNDTYPE_DEPENDENT = 6, 225 | 226 | /* 227 | * PINNED HANDLES for asynchronous operation 228 | * 229 | * Pinned handles are strong handles which have the added property that they 230 | * prevent an object from moving during a garbage collection cycle. This is 231 | * useful when passing a pointer to object innards out of the runtime while GC 232 | * may be enabled. 233 | * 234 | * NOTE: PINNING AN OBJECT IS EXPENSIVE AS IT PREVENTS THE GC FROM ACHIEVING 235 | * OPTIMAL PACKING OF OBJECTS DURING EPHEMERAL COLLECTIONS. THIS TYPE 236 | * OF HANDLE SHOULD BE USED SPARINGLY! 237 | */ 238 | HNDTYPE_ASYNCPINNED = 7, 239 | 240 | /* 241 | * SIZEDREF HANDLES 242 | * 243 | * SizedRef handles are strong handles. Each handle has a piece of user data associated 244 | * with it that stores the size of the object this handle refers to. These handles 245 | * are scanned as strong roots during each GC but only during full GCs would the size 246 | * be calculated. 247 | * 248 | */ 249 | HNDTYPE_SIZEDREF = 8, 250 | 251 | /* 252 | * NATIVE WEAK HANDLES 253 | * 254 | * Native weak reference handles hold two different types of weak handles to any 255 | * RCW with an underlying COM object that implements IWeakReferenceSource. The 256 | * object reference itself is a short weak handle to the RCW. In addition an 257 | * IWeakReference* to the underlying COM object is stored, allowing the handle 258 | * to create a new RCW if the existing RCW is collected. This ensures that any 259 | * code holding onto a native weak reference can always access an RCW to the 260 | * underlying COM object as long as it has not been released by all of its strong 261 | * references. 262 | */ 263 | HNDTYPE_WEAK_NATIVE_COM = 9 264 | } 265 | 266 | // Arguments to GCToEEInterface::StompWriteBarrier 267 | public unsafe struct WriteBarrierParameters 268 | { 269 | // The operation that StompWriteBarrier will perform. 270 | public WriteBarrierOp operation; 271 | 272 | // Whether or not the runtime is currently suspended. If it is not, 273 | // the EE will need to suspend it before bashing the write barrier. 274 | // Used for all operations. 275 | public bool is_runtime_suspended; 276 | 277 | // Whether or not the GC has moved the ephemeral generation to no longer 278 | // be at the top of the heap. When the ephemeral generation is at the top 279 | // of the heap, and the write barrier observes that a pointer is greater than 280 | // g_ephemeral_low, it does not need to check that the pointer is less than 281 | // g_ephemeral_high because there is nothing in the GC heap above the ephemeral 282 | // generation. When this is not the case, however, the GC must inform the EE 283 | // so that the EE can switch to a write barrier that checks that a pointer 284 | // is both greater than g_ephemeral_low and less than g_ephemeral_high. 285 | // Used for WriteBarrierOp::StompResize. 286 | public bool requires_upper_bounds_check; 287 | 288 | // The new card table location. May or may not be the same as the previous 289 | // card table. Used for WriteBarrierOp::Initialize and WriteBarrierOp::StompResize. 290 | public uint* card_table; 291 | 292 | // The new card bundle table location. May or may not be the same as the previous 293 | // card bundle table. Used for WriteBarrierOp::Initialize and WriteBarrierOp::StompResize. 294 | public uint* card_bundle_table; 295 | 296 | // The heap's new low boundary. May or may not be the same as the previous 297 | // value. Used for WriteBarrierOp::Initialize and WriteBarrierOp::StompResize. 298 | public byte* lowest_address; 299 | 300 | // The heap's new high boundary. May or may not be the same as the previous 301 | // value. Used for WriteBarrierOp::Initialize and WriteBarrierOp::StompResize. 302 | public byte* highest_address; 303 | 304 | // The new start of the ephemeral generation. 305 | // Used for WriteBarrierOp::StompEphemeral. 306 | public byte* ephemeral_low; 307 | 308 | // The new end of the ephemeral generation. 309 | // Used for WriteBarrierOp::StompEphemeral. 310 | public byte* ephemeral_high; 311 | 312 | // The new write watch table, if we are using our own write watch 313 | // implementation. Used for WriteBarrierOp::SwitchToWriteWatch only. 314 | public byte* write_watch_table; 315 | }; 316 | 317 | // Different operations that can be done by GCToEEInterface::StompWriteBarrier 318 | public enum WriteBarrierOp 319 | { 320 | StompResize, 321 | StompEphemeral, 322 | Initialize, 323 | SwitchToWriteWatch, 324 | SwitchToNonWriteWatch 325 | }; 326 | 327 | /// 328 | /// A representation of CLR's CLRDATA_ADDRESS, which is a signed 64bit integer. 329 | /// Unfortunately this can cause issues when inspecting 32bit processes, since 330 | /// if the highest bit is set the value will be sign-extended. This struct is 331 | /// meant to 332 | /// 333 | [DebuggerDisplay("{AsUInt64()}")] 334 | public readonly struct CLRDATA_ADDRESS 335 | { 336 | /// 337 | /// Gets raw value of this address. May be sign-extended if inspecting a 32bit process. 338 | /// 339 | public long Value { get; } 340 | 341 | /// Creates an instance of CLRDATA_ADDRESS. 342 | /// 343 | public CLRDATA_ADDRESS(long value) => this.Value = value; 344 | 345 | /// 346 | /// Returns the value of this address and un-sign extends the value if appropriate. 347 | /// 348 | /// The address to convert. 349 | public static implicit operator ulong(CLRDATA_ADDRESS cda) => cda.AsUInt64(); 350 | 351 | public static implicit operator CLRDATA_ADDRESS(ulong value) => new(unchecked((nint)value)); 352 | 353 | public override string ToString() => AsUInt64().ToString("x2"); 354 | 355 | /// 356 | /// Returns the value of this address and un-sign extends the value if appropriate. 357 | /// 358 | /// The value of this address and un-sign extends the value if appropriate. 359 | private ulong AsUInt64() => unchecked((nuint)Value); 360 | } 361 | 362 | public enum CorDebugPlatform : uint 363 | { 364 | CORDB_PLATFORM_WINDOWS_X86 = 0, 365 | CORDB_PLATFORM_WINDOWS_AMD64 = (CORDB_PLATFORM_WINDOWS_X86 + 1), 366 | CORDB_PLATFORM_WINDOWS_IA64 = (CORDB_PLATFORM_WINDOWS_AMD64 + 1), 367 | CORDB_PLATFORM_MAC_PPC = (CORDB_PLATFORM_WINDOWS_IA64 + 1), 368 | CORDB_PLATFORM_MAC_X86 = (CORDB_PLATFORM_MAC_PPC + 1), 369 | CORDB_PLATFORM_WINDOWS_ARM = (CORDB_PLATFORM_MAC_X86 + 1), 370 | CORDB_PLATFORM_MAC_AMD64 = (CORDB_PLATFORM_WINDOWS_ARM + 1), 371 | CORDB_PLATFORM_WINDOWS_ARM64 = (CORDB_PLATFORM_MAC_AMD64 + 1), 372 | CORDB_PLATFORM_POSIX_AMD64 = (CORDB_PLATFORM_WINDOWS_ARM64 + 1), 373 | CORDB_PLATFORM_POSIX_X86 = (CORDB_PLATFORM_POSIX_AMD64 + 1), 374 | CORDB_PLATFORM_POSIX_ARM = (CORDB_PLATFORM_POSIX_X86 + 1), 375 | CORDB_PLATFORM_POSIX_ARM64 = (CORDB_PLATFORM_POSIX_ARM + 1) 376 | } 377 | 378 | public struct DacpThreadStoreData 379 | { 380 | public int threadCount; 381 | public int unstartedThreadCount; 382 | public int backgroundThreadCount; 383 | public int pendingThreadCount; 384 | public int deadThreadCount; 385 | public CLRDATA_ADDRESS firstThread; 386 | public CLRDATA_ADDRESS finalizerThread; 387 | public CLRDATA_ADDRESS gcThread; 388 | public int fHostConfig; // Uses hosting flags defined above 389 | } 390 | 391 | public struct DacpAppDomainStoreData 392 | { 393 | public CLRDATA_ADDRESS sharedDomain; 394 | public CLRDATA_ADDRESS systemDomain; 395 | public int DomainCount; 396 | } 397 | 398 | public struct DacpCOMInterfacePointerData 399 | { 400 | public CLRDATA_ADDRESS methodTable; 401 | public CLRDATA_ADDRESS interfacePtr; 402 | public CLRDATA_ADDRESS comContext; 403 | } 404 | 405 | public enum DacpAppDomainDataStage 406 | { 407 | STAGE_CREATING, 408 | STAGE_READYFORMANAGEDCODE, 409 | STAGE_ACTIVE, 410 | STAGE_OPEN, 411 | STAGE_UNLOAD_REQUESTED, 412 | STAGE_EXITING, 413 | STAGE_EXITED, 414 | STAGE_FINALIZING, 415 | STAGE_FINALIZED, 416 | STAGE_HANDLETABLE_NOACCESS, 417 | STAGE_CLEARED, 418 | STAGE_COLLECTED, 419 | STAGE_CLOSED 420 | } 421 | 422 | // Information about a BaseDomain (AppDomain, SharedDomain or SystemDomain). 423 | // For types other than AppDomain, some fields (like dwID, DomainLocalBlock, etc.) will be 0/null. 424 | public struct DacpAppDomainData 425 | { 426 | // The pointer to the BaseDomain (not necessarily an AppDomain). 427 | // It's useful to keep this around in the structure 428 | public CLRDATA_ADDRESS AppDomainPtr; 429 | public CLRDATA_ADDRESS AppSecDesc; 430 | public CLRDATA_ADDRESS pLowFrequencyHeap; 431 | public CLRDATA_ADDRESS pHighFrequencyHeap; 432 | public CLRDATA_ADDRESS pStubHeap; 433 | public CLRDATA_ADDRESS DomainLocalBlock; 434 | public CLRDATA_ADDRESS pDomainLocalModules; 435 | // The creation sequence number of this app domain (starting from 1) 436 | public int dwId; 437 | public int AssemblyCount; 438 | public int FailedAssemblyCount; 439 | public DacpAppDomainDataStage appDomainStage; 440 | } 441 | 442 | public struct DacpAssemblyData 443 | { 444 | public CLRDATA_ADDRESS AssemblyPtr; //useful to have 445 | public CLRDATA_ADDRESS ClassLoader; 446 | public CLRDATA_ADDRESS ParentDomain; 447 | public CLRDATA_ADDRESS BaseDomainPtr; 448 | public CLRDATA_ADDRESS AssemblySecDesc; 449 | public bool isDynamic; 450 | public uint ModuleCount; 451 | public uint LoadContext; 452 | public bool isDomainNeutral; // Always false, preserved for backward compatibility 453 | public int dwLocationFlags; 454 | } 455 | 456 | public struct DacpThreadData 457 | { 458 | public int corThreadId; 459 | public int osThreadId; 460 | public int state; 461 | public ulong preemptiveGCDisabled; 462 | public CLRDATA_ADDRESS allocContextPtr; 463 | public CLRDATA_ADDRESS allocContextLimit; 464 | public CLRDATA_ADDRESS context; 465 | public CLRDATA_ADDRESS domain; 466 | public CLRDATA_ADDRESS pFrame; 467 | public int lockCount; 468 | public CLRDATA_ADDRESS firstNestedException; // Pass this pointer to DacpNestedExceptionInfo 469 | public CLRDATA_ADDRESS teb; 470 | public CLRDATA_ADDRESS fiberData; 471 | public CLRDATA_ADDRESS lastThrownObjectHandle; 472 | public CLRDATA_ADDRESS nextThread; 473 | } 474 | 475 | public struct DacpModuleData 476 | { 477 | public CLRDATA_ADDRESS Address; 478 | public CLRDATA_ADDRESS File; // A PEFile addr 479 | public CLRDATA_ADDRESS ilBase; 480 | public CLRDATA_ADDRESS metadataStart; 481 | public ULONG64 metadataSize; 482 | public CLRDATA_ADDRESS Assembly; // Assembly pointer 483 | public BOOL bIsReflection; 484 | public BOOL bIsPEFile; 485 | public ULONG64 dwBaseClassIndex; 486 | public ULONG64 dwModuleID; 487 | 488 | public DWORD dwTransientFlags; 489 | 490 | public CLRDATA_ADDRESS TypeDefToMethodTableMap; 491 | public CLRDATA_ADDRESS TypeRefToMethodTableMap; 492 | public CLRDATA_ADDRESS MethodDefToDescMap; 493 | public CLRDATA_ADDRESS FieldDefToDescMap; 494 | public CLRDATA_ADDRESS MemberRefToDescMap; 495 | public CLRDATA_ADDRESS FileReferencesMap; 496 | public CLRDATA_ADDRESS ManifestModuleReferencesMap; 497 | 498 | CLRDATA_ADDRESS pLookupTableHeap; 499 | CLRDATA_ADDRESS pThunkHeap; 500 | 501 | public ULONG64 dwModuleIndex; 502 | } 503 | 504 | public enum ModuleMapType { TYPEDEFTOMETHODTABLE, TYPEREFTOMETHODTABLE } 505 | 506 | 507 | public struct DacpMethodDescData 508 | { 509 | public BOOL bHasNativeCode; 510 | public BOOL bIsDynamic; 511 | public WORD wSlotNumber; 512 | public CLRDATA_ADDRESS NativeCodeAddr; 513 | // Useful for breaking when a method is jitted. 514 | public CLRDATA_ADDRESS AddressOfNativeCodeSlot; 515 | 516 | public CLRDATA_ADDRESS MethodDescPtr; 517 | public CLRDATA_ADDRESS MethodTablePtr; 518 | public CLRDATA_ADDRESS ModulePtr; 519 | 520 | public MdToken MDToken; 521 | public CLRDATA_ADDRESS GCInfo; 522 | public CLRDATA_ADDRESS GCStressCodeCopy; 523 | 524 | // This is only valid if bIsDynamic is true 525 | public CLRDATA_ADDRESS managedDynamicMethodObject; 526 | 527 | public CLRDATA_ADDRESS requestedIP; 528 | 529 | // Gives info for the single currently active version of a method 530 | public DacpReJitData rejitDataCurrent; 531 | 532 | // Gives info corresponding to requestedIP (for !ip2md) 533 | public DacpReJitData rejitDataRequested; 534 | 535 | // Total number of rejit versions that have been jitted 536 | public ULONG cJittedRejitVersions; 537 | } 538 | 539 | public struct MdToken 540 | { 541 | public int Value; 542 | } 543 | 544 | public struct DacpReJitData 545 | { 546 | public enum Flags 547 | { 548 | kUnknown, 549 | kRequested, 550 | kActive, 551 | kReverted, 552 | }; 553 | 554 | public CLRDATA_ADDRESS rejitID; 555 | public Flags flags; 556 | public CLRDATA_ADDRESS NativeCodeAddr; 557 | } 558 | 559 | public struct DacpMethodDescTransparencyData 560 | { 561 | public BOOL bHasCriticalTransparentInfo; 562 | public BOOL bIsCritical; 563 | public BOOL bIsTreatAsSafe; 564 | } 565 | 566 | public enum JITTypes { TYPE_UNKNOWN = 0, TYPE_JIT, TYPE_PJIT }; 567 | 568 | public struct DacpCodeHeaderData 569 | { 570 | public CLRDATA_ADDRESS GCInfo; 571 | public JITTypes JITType; 572 | public CLRDATA_ADDRESS MethodDescPtr; 573 | public CLRDATA_ADDRESS MethodStart; 574 | public DWORD MethodSize; 575 | public CLRDATA_ADDRESS ColdRegionStart; 576 | public DWORD ColdRegionSize; 577 | public DWORD HotRegionSize; 578 | } 579 | 580 | public struct DacpJitManagerInfo 581 | { 582 | public CLRDATA_ADDRESS managerAddr; 583 | public DWORD codeType; // for union below 584 | public CLRDATA_ADDRESS ptrHeapList; // A HeapList * if IsMiIL(codeType) 585 | } 586 | 587 | public struct T_CONTEXT 588 | { 589 | public int Value; 590 | } 591 | 592 | public struct DacpThreadpoolData 593 | { 594 | public LONG cpuUtilization; 595 | public int NumIdleWorkerThreads; 596 | public int NumWorkingWorkerThreads; 597 | public int NumRetiredWorkerThreads; 598 | public LONG MinLimitTotalWorkerThreads; 599 | public LONG MaxLimitTotalWorkerThreads; 600 | 601 | public CLRDATA_ADDRESS FirstUnmanagedWorkRequest; 602 | 603 | public CLRDATA_ADDRESS HillClimbingLog; 604 | public int HillClimbingLogFirstIndex; 605 | public int HillClimbingLogSize; 606 | 607 | public DWORD NumTimers; 608 | 609 | public LONG NumCPThreads; 610 | public LONG NumFreeCPThreads; 611 | public LONG MaxFreeCPThreads; 612 | public LONG NumRetiredCPThreads; 613 | public LONG MaxLimitTotalCPThreads; 614 | public LONG CurrentLimitTotalCPThreads; 615 | public LONG MinLimitTotalCPThreads; 616 | 617 | public CLRDATA_ADDRESS AsyncTimerCallbackCompletionFPtr; 618 | } 619 | 620 | public struct DacpGenerationData 621 | { 622 | public CLRDATA_ADDRESS start_segment; 623 | public CLRDATA_ADDRESS allocation_start; 624 | 625 | // These are examined only for generation 0, otherwise NULL 626 | public CLRDATA_ADDRESS allocContextPtr; 627 | public CLRDATA_ADDRESS allocContextLimit; 628 | } 629 | 630 | public struct DacpWorkRequestData 631 | { 632 | public CLRDATA_ADDRESS Function; 633 | public CLRDATA_ADDRESS Context; 634 | public CLRDATA_ADDRESS NextWorkRequest; 635 | } 636 | 637 | public struct DacpHillClimbingLogEntry 638 | { 639 | public DWORD TickCount; 640 | public int Transition; 641 | public int NewControlSetting; 642 | public int LastHistoryCount; 643 | public double LastHistoryMean; 644 | } 645 | 646 | public enum DacpObjectType { OBJ_STRING = 0, OBJ_FREE, OBJ_OBJECT, OBJ_ARRAY, OBJ_OTHER }; 647 | 648 | public readonly struct CorElementType 649 | { 650 | public readonly uint Value; 651 | } 652 | 653 | public struct DacpObjectData 654 | { 655 | public CLRDATA_ADDRESS MethodTable; 656 | public DacpObjectType ObjectType; 657 | public ULONG64 Size; 658 | public CLRDATA_ADDRESS ElementTypeHandle; 659 | public CorElementType ElementType; 660 | public DWORD dwRank; 661 | public ULONG64 dwNumComponents; 662 | public ULONG64 dwComponentSize; 663 | public CLRDATA_ADDRESS ArrayDataPtr; 664 | public CLRDATA_ADDRESS ArrayBoundsPtr; 665 | public CLRDATA_ADDRESS ArrayLowerBoundsPtr; 666 | 667 | public CLRDATA_ADDRESS RCW; 668 | public CLRDATA_ADDRESS CCW; 669 | } 670 | 671 | public struct DacpMethodTableData 672 | { 673 | public BOOL bIsFree; // everything else is NULL if this is true. 674 | public CLRDATA_ADDRESS Module; 675 | public CLRDATA_ADDRESS Class; 676 | public CLRDATA_ADDRESS ParentMethodTable; 677 | public WORD wNumInterfaces; 678 | public WORD wNumMethods; 679 | public WORD wNumVtableSlots; 680 | public WORD wNumVirtuals; 681 | public DWORD BaseSize; 682 | public DWORD ComponentSize; 683 | public MdTypeDef cl; // Metadata token 684 | public DWORD dwAttrClass; // cached metadata 685 | public BOOL bIsShared; // Always false, preserved for backward compatibility 686 | public BOOL bIsDynamic; 687 | public BOOL bContainsPointers; 688 | } 689 | 690 | public readonly struct MdTypeDef 691 | { 692 | public readonly int Value; 693 | } 694 | 695 | public struct DacpMethodTableFieldData 696 | { 697 | public WORD wNumInstanceFields; 698 | public WORD wNumStaticFields; 699 | public WORD wNumThreadStaticFields; 700 | 701 | public CLRDATA_ADDRESS FirstField; // If non-null, you can retrieve more 702 | 703 | public WORD wContextStaticOffset; 704 | public WORD wContextStaticsSize; 705 | } 706 | 707 | public struct DacpMethodTableTransparencyData 708 | { 709 | public BOOL bHasCriticalTransparentInfo; 710 | public BOOL bIsCritical; 711 | public BOOL bIsTreatAsSafe; 712 | } 713 | 714 | public struct DacpFieldDescData 715 | { 716 | public CorElementType Type; 717 | public CorElementType sigType; // ELEMENT_TYPE_XXX from signature. We need this to disply pretty name for String in minidump's case 718 | public CLRDATA_ADDRESS MTOfType; // NULL if Type is not loaded 719 | 720 | public CLRDATA_ADDRESS ModuleOfType; 721 | public MdTypeDef TokenOfType; 722 | 723 | public MdFieldDef mb; 724 | public CLRDATA_ADDRESS MTOfEnclosingClass; 725 | public DWORD dwOffset; 726 | public BOOL bIsThreadLocal; 727 | public BOOL bIsContextLocal; 728 | public BOOL bIsStatic; 729 | public CLRDATA_ADDRESS NextField; 730 | } 731 | 732 | public readonly struct MdFieldDef 733 | { 734 | public readonly int Value; 735 | } 736 | 737 | public struct DacpGcHeapData 738 | { 739 | public BOOL bServerMode; 740 | public BOOL bGcStructuresValid; 741 | public UINT HeapCount; 742 | public UINT g_max_generation; 743 | } 744 | 745 | public struct DacpGcHeapDetails 746 | { 747 | public const int DAC_NUMBERGENERATIONS = 4; 748 | public CLRDATA_ADDRESS heapAddr; // Only filled in in server mode, otherwise NULL 749 | public CLRDATA_ADDRESS alloc_allocated; 750 | 751 | public CLRDATA_ADDRESS mark_array; 752 | public CLRDATA_ADDRESS current_c_gc_state; 753 | public CLRDATA_ADDRESS next_sweep_obj; 754 | public CLRDATA_ADDRESS saved_sweep_ephemeral_seg; 755 | public CLRDATA_ADDRESS saved_sweep_ephemeral_start; 756 | public CLRDATA_ADDRESS background_saved_lowest_address; 757 | public CLRDATA_ADDRESS background_saved_highest_address; 758 | 759 | public DacpGenerationData generation_table1; 760 | public DacpGenerationData generation_table2; 761 | public DacpGenerationData generation_table3; 762 | public DacpGenerationData generation_table4; 763 | public CLRDATA_ADDRESS ephemeral_heap_segment; 764 | public CLRDATA_ADDRESS finalization_fill_pointers1; 765 | public CLRDATA_ADDRESS finalization_fill_pointers2; 766 | public CLRDATA_ADDRESS finalization_fill_pointers3; 767 | public CLRDATA_ADDRESS finalization_fill_pointers4; 768 | public CLRDATA_ADDRESS finalization_fill_pointers5; 769 | public CLRDATA_ADDRESS finalization_fill_pointers6; 770 | public CLRDATA_ADDRESS finalization_fill_pointers7; 771 | public CLRDATA_ADDRESS lowest_address; 772 | public CLRDATA_ADDRESS highest_address; 773 | public CLRDATA_ADDRESS card_table; 774 | } 775 | 776 | public struct DacpHeapSegmentData 777 | { 778 | public CLRDATA_ADDRESS segmentAddr; 779 | public CLRDATA_ADDRESS allocated; 780 | public CLRDATA_ADDRESS committed; 781 | public CLRDATA_ADDRESS reserved; 782 | public CLRDATA_ADDRESS used; 783 | public CLRDATA_ADDRESS mem; 784 | // pass this to request if non-null to get the next segments. 785 | public CLRDATA_ADDRESS next; 786 | public CLRDATA_ADDRESS gc_heap; // only filled in in server mode, otherwise NULL 787 | // computed field: if this is the ephemeral segment highMark includes the ephemeral generation 788 | public CLRDATA_ADDRESS highAllocMark; 789 | 790 | public nint flags; 791 | public CLRDATA_ADDRESS background_allocated; 792 | } 793 | 794 | public struct DacpOomData 795 | { 796 | public int reason; 797 | public ULONG64 alloc_size; 798 | public ULONG64 available_pagefile_mb; 799 | public ULONG64 gc_index; 800 | public int fgm; 801 | public ULONG64 size; 802 | public BOOL loh_p; 803 | } 804 | 805 | public struct DacpGcHeapAnalyzeData 806 | { 807 | public CLRDATA_ADDRESS heapAddr; // Only filled in in server mode, otherwise NULL 808 | 809 | public CLRDATA_ADDRESS internal_root_array; 810 | public ULONG64 internal_root_array_index; 811 | public BOOL heap_analyze_success; 812 | } 813 | 814 | public struct DacpSyncBlockData 815 | { 816 | public CLRDATA_ADDRESS Object; 817 | public BOOL bFree; // if set, no other fields are useful 818 | 819 | // fields below provide data from this, so it's just for display 820 | public CLRDATA_ADDRESS SyncBlockPointer; 821 | public DWORD COMFlags; 822 | public UINT MonitorHeld; 823 | public UINT Recursion; 824 | public CLRDATA_ADDRESS HoldingThread; 825 | public UINT AdditionalThreadCount; 826 | public CLRDATA_ADDRESS appDomainPtr; 827 | 828 | // SyncBlockCount will always be filled in with the number of SyncBlocks. 829 | // SyncBlocks may be requested from [1,SyncBlockCount] 830 | public UINT SyncBlockCount; 831 | } 832 | 833 | public struct DacpDomainLocalModuleData 834 | { 835 | // These two parameters are used as input params when calling the 836 | // no-argument form of Request below. 837 | public CLRDATA_ADDRESS appDomainAddr; 838 | public ULONG64 ModuleID; 839 | 840 | public CLRDATA_ADDRESS pClassData; 841 | public CLRDATA_ADDRESS pDynamicClassTable; 842 | public CLRDATA_ADDRESS pGCStaticDataStart; 843 | public CLRDATA_ADDRESS pNonGCStaticDataStart; 844 | } 845 | 846 | public struct DacpThreadLocalModuleData 847 | { 848 | // These two parameters are used as input params when calling the 849 | // no-argument form of Request below. 850 | public CLRDATA_ADDRESS threadAddr; 851 | public ULONG64 ModuleIndex; 852 | 853 | public CLRDATA_ADDRESS pClassData; 854 | public CLRDATA_ADDRESS pDynamicClassTable; 855 | public CLRDATA_ADDRESS pGCStaticDataStart; 856 | public CLRDATA_ADDRESS pNonGCStaticDataStart; 857 | } 858 | 859 | public struct DacpSyncBlockCleanupData 860 | { 861 | public CLRDATA_ADDRESS SyncBlockPointer; 862 | 863 | public CLRDATA_ADDRESS nextSyncBlock; 864 | public CLRDATA_ADDRESS blockRCW; 865 | public CLRDATA_ADDRESS blockClassFactory; 866 | public CLRDATA_ADDRESS blockCCW; 867 | } 868 | 869 | public enum VCSHeapType { IndcellHeap, LookupHeap, ResolveHeap, DispatchHeap, CacheEntryHeap } 870 | 871 | public struct DacpUsefulGlobalsData 872 | { 873 | public CLRDATA_ADDRESS ArrayMethodTable; 874 | public CLRDATA_ADDRESS StringMethodTable; 875 | public CLRDATA_ADDRESS ObjectMethodTable; 876 | public CLRDATA_ADDRESS ExceptionMethodTable; 877 | public CLRDATA_ADDRESS FreeMethodTable; 878 | } 879 | 880 | public readonly struct HMODULE 881 | { 882 | public readonly nint Value; 883 | } 884 | 885 | public struct DacpRCWData 886 | { 887 | public CLRDATA_ADDRESS identityPointer; 888 | public CLRDATA_ADDRESS unknownPointer; 889 | public CLRDATA_ADDRESS managedObject; 890 | public CLRDATA_ADDRESS jupiterObject; 891 | public CLRDATA_ADDRESS vtablePtr; 892 | public CLRDATA_ADDRESS creatorThread; 893 | public CLRDATA_ADDRESS ctxCookie; 894 | 895 | public LONG refCount; 896 | public LONG interfaceCount; 897 | 898 | public BOOL isJupiterObject; 899 | public BOOL supportsIInspectable; 900 | public BOOL isAggregated; 901 | public BOOL isContained; 902 | public BOOL isFreeThreaded; 903 | public BOOL isDisconnected; 904 | } 905 | 906 | public struct DacpCCWData 907 | { 908 | public CLRDATA_ADDRESS outerIUnknown; 909 | public CLRDATA_ADDRESS managedObject; 910 | public CLRDATA_ADDRESS handle; 911 | public CLRDATA_ADDRESS ccwAddress; 912 | 913 | public LONG refCount; 914 | public LONG interfaceCount; 915 | public BOOL isNeutered; 916 | 917 | public LONG jupiterRefCount; 918 | public BOOL isPegged; 919 | public BOOL isGlobalPegged; 920 | public BOOL hasStrongRef; 921 | public BOOL isExtendsCOMObject; 922 | public BOOL isAggregated; 923 | } 924 | 925 | public struct DacpAllocData 926 | { 927 | public CLRDATA_ADDRESS allocBytes; 928 | public CLRDATA_ADDRESS allocBytesLoh; 929 | }; 930 | 931 | public struct DacpGenerationAllocData 932 | { 933 | public DacpAllocData allocData1; 934 | public DacpAllocData allocData2; 935 | public DacpAllocData allocData3; 936 | public DacpAllocData allocData4; 937 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/DllMain.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Runtime.InteropServices; 3 | 4 | using static ManagedDotnetGC.Log; 5 | 6 | namespace ManagedDotnetGC; 7 | 8 | public class DllMain 9 | { 10 | [UnmanagedCallersOnly(EntryPoint = "GC_Initialize")] 11 | public static unsafe HResult GC_Initialize(IntPtr clrToGC, IntPtr* gcHeap, IntPtr* gcHandleManager, GcDacVars* gcDacVars) 12 | { 13 | Write("GC_Initialize"); 14 | 15 | var gc = new GCHeap(NativeObjects.IGCToCLR.Wrap(clrToGC)); 16 | 17 | *gcHeap = gc.IGCHeapObject; 18 | *gcHandleManager = gc.IGCHandleManagerObject; 19 | 20 | return HResult.S_OK; 21 | } 22 | 23 | [UnmanagedCallersOnly(EntryPoint = "GC_VersionInfo", CallConvs = new[] { typeof(CallConvCdecl) })] 24 | public static unsafe void GC_VersionInfo(VersionInfo* versionInfo) 25 | { 26 | Write($"GC_VersionInfo {versionInfo->MajorVersion}.{versionInfo->MinorVersion}.{versionInfo->BuildVersion}"); 27 | 28 | versionInfo->MajorVersion = 5; 29 | versionInfo->MinorVersion = 3; 30 | } 31 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/GCDesc.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace ManagedDotnetGC; 4 | 5 | public readonly unsafe struct GCDesc 6 | { 7 | private static readonly int s_GCDescSize = IntPtr.Size * 2; 8 | 9 | private readonly byte* _data; 10 | private readonly int _size; 11 | 12 | public GCDesc(byte* data, int size) 13 | { 14 | _data = data; 15 | _size = size; 16 | } 17 | 18 | public IEnumerable<(IntPtr ReferencedObject, int Offset)> WalkObject(IntPtr buffer, int size) 19 | { 20 | int series = GetNumSeries(); 21 | int highest = GetHighestSeries(); 22 | int curr = highest; 23 | 24 | if (series > 0) 25 | { 26 | int lowest = GetLowestSeries(); 27 | do 28 | { 29 | long offset = GetSeriesOffset(curr); 30 | long stop = offset + GetSeriesSize(curr) + size; 31 | 32 | while (offset < stop) 33 | { 34 | var ret = Marshal.ReadIntPtr(buffer, (int)offset); 35 | if (ret != IntPtr.Zero) 36 | yield return (ret, (int)offset); 37 | 38 | offset += IntPtr.Size; 39 | } 40 | 41 | curr -= s_GCDescSize; 42 | } while (curr >= lowest); 43 | } 44 | else 45 | { 46 | long offset = GetSeriesOffset(curr); 47 | 48 | while (offset < size - IntPtr.Size) 49 | { 50 | for (int i = 0; i > series; i--) 51 | { 52 | int nptrs = GetPointers(curr, i); 53 | int skip = GetSkip(curr, i); 54 | 55 | long stop = offset + (nptrs * IntPtr.Size); 56 | do 57 | { 58 | var ret = Marshal.ReadIntPtr(buffer, (int)offset); 59 | if (ret != IntPtr.Zero) 60 | yield return (ret, (int)offset); 61 | 62 | offset += IntPtr.Size; 63 | } while (offset < stop); 64 | 65 | offset += skip; 66 | } 67 | } 68 | } 69 | } 70 | 71 | private int GetPointers(int curr, int i) 72 | { 73 | int offset = i * IntPtr.Size; 74 | 75 | if (IntPtr.Size == 4) 76 | { 77 | return *(short*)(_data + curr + offset); 78 | } 79 | 80 | return *(int*)(_data + curr + offset); 81 | } 82 | 83 | private int GetSkip(int curr, int i) 84 | { 85 | int offset = i * IntPtr.Size + IntPtr.Size / 2; 86 | 87 | if (IntPtr.Size == 4) 88 | { 89 | return *(short*)(_data + curr + offset); 90 | } 91 | 92 | return *(int*)(_data + curr + offset); 93 | } 94 | 95 | private int GetSeriesSize(int curr) 96 | { 97 | if (IntPtr.Size == 4) 98 | { 99 | return *(int*)(_data + curr); 100 | } 101 | 102 | return (int)*(long*)(_data + curr); 103 | } 104 | 105 | private long GetSeriesOffset(int curr) 106 | { 107 | long offset; 108 | 109 | if (IntPtr.Size == 4) 110 | { 111 | offset = *(uint*)(_data + curr + IntPtr.Size); 112 | } 113 | else 114 | { 115 | offset = *(long*)(_data + curr + IntPtr.Size); 116 | } 117 | 118 | return offset; 119 | } 120 | 121 | private int GetHighestSeries() 122 | { 123 | return _size - IntPtr.Size * 3; 124 | } 125 | 126 | private int GetLowestSeries() 127 | { 128 | return _size - ComputeSize(GetNumSeries()); 129 | } 130 | 131 | private static int ComputeSize(int series) 132 | { 133 | return IntPtr.Size + series * IntPtr.Size * 2; 134 | } 135 | 136 | private int GetNumSeries() 137 | { 138 | if (IntPtr.Size == 4) 139 | { 140 | return *(int*)(_data + _size - IntPtr.Size); 141 | } 142 | 143 | return (int)*(long*)(_data + _size - IntPtr.Size); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /ManagedDotnetGC/GCHandleManager.cs: -------------------------------------------------------------------------------- 1 | using ManagedDotnetGC.Interfaces; 2 | using static ManagedDotnetGC.Log; 3 | 4 | namespace ManagedDotnetGC; 5 | 6 | internal unsafe class GCHandleManager : IGCHandleManager 7 | { 8 | private readonly NativeObjects.IGCToCLRInvoker _gcToClr; 9 | private readonly NativeObjects.IGCHandleManager _nativeObject; 10 | private readonly GCHandleStore _gcHandleStore; 11 | 12 | 13 | public GCHandleManager(NativeObjects.IGCToCLRInvoker gcToClr) 14 | { 15 | _gcHandleStore = new GCHandleStore(); 16 | _gcToClr = gcToClr; 17 | _nativeObject = NativeObjects.IGCHandleManager.Wrap(this); 18 | } 19 | 20 | public IntPtr IGCHandleManagerObject => _nativeObject; 21 | 22 | public GCHandleStore Store => _gcHandleStore; 23 | 24 | public bool Initialize() 25 | { 26 | Write("GCHandleManager Initialize"); 27 | return true; 28 | } 29 | 30 | public void Shutdown() 31 | { 32 | } 33 | 34 | public IntPtr GetGlobalHandleStore() 35 | { 36 | return _gcHandleStore.IGCHandleStoreObject; 37 | } 38 | 39 | public IntPtr CreateHandleStore() 40 | { 41 | Write("GCHandleManager CreateHandleStore"); 42 | 43 | return default; 44 | } 45 | 46 | public void DestroyHandleStore(IntPtr store) 47 | { 48 | Write("GCHandleManager DestroyHandleStore"); 49 | } 50 | 51 | public unsafe OBJECTHANDLE CreateGlobalHandleOfType(GCObject* obj, HandleType type) 52 | { 53 | return _gcHandleStore.CreateHandleOfType(obj, type); 54 | } 55 | 56 | public OBJECTHANDLE CreateDuplicateHandle(OBJECTHANDLE handle) 57 | { 58 | Write("GCHandleManager CreateDuplicateHandle"); 59 | 60 | return handle; 61 | } 62 | 63 | public void DestroyHandleOfType(OBJECTHANDLE handle, HandleType type) 64 | { 65 | Write("GCHandleManager DestroyHandleOfType"); 66 | } 67 | 68 | public void DestroyHandleOfUnknownType(OBJECTHANDLE handle) 69 | { 70 | Write("GCHandleManager DestroyHandleOfUnknownType"); 71 | } 72 | 73 | public unsafe void SetExtraInfoForHandle(OBJECTHANDLE handle, HandleType type, void* pExtraInfo) 74 | { 75 | Write("GCHandleManager SetExtraInfoForHandle"); 76 | } 77 | 78 | public unsafe void* GetExtraInfoFromHandle(OBJECTHANDLE handle) 79 | { 80 | Write("GCHandleManager GetExtraInfoFromHandle"); 81 | return null; 82 | } 83 | 84 | public unsafe void StoreObjectInHandle(OBJECTHANDLE handle, GCObject* obj) 85 | { 86 | Write($"GCHandleManager StoreObjectInHandle {handle} {(IntPtr)obj:x2}"); 87 | handle.SetObject((nint)obj); 88 | } 89 | 90 | public unsafe bool StoreObjectInHandleIfNull(OBJECTHANDLE handle, GCObject* obj) 91 | { 92 | Write("GCHandleManager StoreObjectInHandleIfNull"); 93 | return false; 94 | } 95 | 96 | public unsafe void SetDependentHandleSecondary(OBJECTHANDLE handle, GCObject* obj) 97 | { 98 | Write("GCHandleManager SetDependentHandleSecondary"); 99 | } 100 | 101 | public unsafe GCObject* GetDependentHandleSecondary(OBJECTHANDLE handle) 102 | { 103 | Write("GCHandleManager GetDependentHandleSecondary"); 104 | return null; 105 | } 106 | 107 | public unsafe GCObject* InterlockedCompareExchangeObjectInHandle(OBJECTHANDLE handle, GCObject* obj, GCObject* comparandObject) 108 | { 109 | Write("GCHandleManager InterlockedCompareExchangeObjectInHandle"); 110 | return null; 111 | } 112 | 113 | public HandleType HandleFetchType(OBJECTHANDLE handle) 114 | { 115 | Write("GCHandleManager HandleFetchType"); 116 | return HandleType.HNDTYPE_WEAK_SHORT; 117 | } 118 | 119 | public unsafe void TraceRefCountedHandles(void* callback, uint* param1, uint* param2) 120 | { 121 | Write("GCHandleManager TraceRefCountedHandles"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /ManagedDotnetGC/GCHandleStore.cs: -------------------------------------------------------------------------------- 1 | using ManagedDotnetGC.Dac; 2 | using ManagedDotnetGC.Interfaces; 3 | using System.Runtime.InteropServices; 4 | 5 | using static ManagedDotnetGC.Log; 6 | 7 | namespace ManagedDotnetGC; 8 | 9 | public unsafe class GCHandleStore : IGCHandleStore 10 | { 11 | private readonly NativeObjects.IGCHandleStore _nativeObject; 12 | private readonly nint* _store; 13 | private int _handleCount; 14 | 15 | public GCHandleStore() 16 | { 17 | _nativeObject = NativeObjects.IGCHandleStore.Wrap(this); 18 | _store = (nint*)NativeMemory.AllocZeroed((nuint)sizeof(nint) * 65535); 19 | Write($"GCHandleStore {(IntPtr)_store:x2}"); 20 | } 21 | 22 | public IntPtr IGCHandleStoreObject => _nativeObject; 23 | 24 | public void DumpHandles(DacManager? dacManager) 25 | { 26 | Write("GCHandleStore DumpHandles"); 27 | 28 | for (int i = 0; i < _handleCount; i++) 29 | { 30 | var target = _store[i]; 31 | 32 | if (dacManager == null) 33 | { 34 | Write($"Handle {i} - {target:x2}"); 35 | } 36 | else 37 | { 38 | Write($"Handle {i} - {target:x2} - {dacManager.GetObjectName(new(target))}"); 39 | } 40 | } 41 | } 42 | 43 | public void Uproot() 44 | { 45 | Write("GCHandleStore Uproot"); 46 | } 47 | 48 | public bool ContainsHandle(OBJECTHANDLE handle) 49 | { 50 | Console.WriteLine("GCHandleStore ContainsHandle"); 51 | return false; 52 | } 53 | 54 | public unsafe OBJECTHANDLE CreateHandleOfType(GCObject* obj, HandleType type) 55 | { 56 | Write($"CreateHandleOfType {type} for {(IntPtr)obj:x2}"); 57 | 58 | var handle = GetNextAvailableHandle(); 59 | handle.SetObject((nint)obj); 60 | 61 | Write($"Returning {handle}"); 62 | return handle; 63 | } 64 | 65 | public unsafe OBJECTHANDLE CreateHandleOfType2(GCObject* obj, HandleType type, int heapToAffinitizeTo) 66 | { 67 | Write($"GCHandleStore CreateHandleOfType2 - {(nint)obj:x2}"); 68 | 69 | var handle = GetNextAvailableHandle(); 70 | handle.SetObject((nint)obj); 71 | 72 | Write($"Returning {handle}"); 73 | return handle; 74 | } 75 | 76 | public unsafe OBJECTHANDLE CreateHandleWithExtraInfo(GCObject* obj, HandleType type, void* pExtraInfo) 77 | { 78 | Write("GCHandleStore CreateHandleWithExtraInfo"); 79 | return GetNextAvailableHandle(); 80 | } 81 | 82 | public unsafe OBJECTHANDLE CreateDependentHandle(GCObject* primary, GCObject* secondary) 83 | { 84 | Write("GCHandleStore CreateDependentHandle"); 85 | return GetNextAvailableHandle(); 86 | } 87 | 88 | public void Destructor() 89 | { 90 | Write("GCHandleStore Destructor"); 91 | } 92 | 93 | private OBJECTHANDLE GetNextAvailableHandle() 94 | { 95 | var handle = (nint)(_store + _handleCount); 96 | _handleCount++; 97 | return new(handle); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /ManagedDotnetGC/GCHeap.cs: -------------------------------------------------------------------------------- 1 | using ManagedDotnetGC.Dac; 2 | using NativeObjects; 3 | using System.Runtime.InteropServices; 4 | 5 | using static ManagedDotnetGC.Log; 6 | 7 | namespace ManagedDotnetGC; 8 | 9 | internal unsafe class GCHeap : Interfaces.IGCHeap 10 | { 11 | private readonly IGCToCLRInvoker _gcToClr; 12 | private readonly GCHandleManager _gcHandleManager; 13 | 14 | private readonly IGCHeap _nativeObject; 15 | private DacManager? _dacManager; 16 | 17 | public GCHeap(IGCToCLRInvoker gcToClr) 18 | { 19 | _gcToClr = gcToClr; 20 | _gcHandleManager = new GCHandleManager(gcToClr); 21 | 22 | _nativeObject = IGCHeap.Wrap(this); 23 | } 24 | 25 | public IntPtr IGCHeapObject => _nativeObject; 26 | public IntPtr IGCHandleManagerObject => _gcHandleManager.IGCHandleManagerObject; 27 | 28 | public void Destructor() 29 | { 30 | Write("IGCHeap Destructor"); 31 | } 32 | 33 | public bool IsValidSegmentSize(nint size) 34 | { 35 | Write("IsValidSegmentSize"); 36 | return false; 37 | } 38 | 39 | public bool IsValidGen0MaxSize(nint size) 40 | { 41 | Write("IsValidGen0MaxSize"); 42 | return false; 43 | } 44 | 45 | public nint GetValidSegmentSize(bool large_seg = false) 46 | { 47 | Write("GetValidSegmentSize"); 48 | return 0; 49 | } 50 | 51 | public void SetReservedVMLimit(nint vmlimit) 52 | { 53 | Write("SetReservedVMLimit"); 54 | } 55 | 56 | public void WaitUntilConcurrentGCComplete() 57 | { 58 | Write("WaitUntilConcurrentGCComplete"); 59 | } 60 | 61 | public bool IsConcurrentGCInProgress() 62 | { 63 | Write("IsConcurrentGCInProgress"); 64 | return false; 65 | } 66 | 67 | public void TemporaryEnableConcurrentGC() 68 | { 69 | Write("TemporaryEnableConcurrentGC"); 70 | } 71 | 72 | public void TemporaryDisableConcurrentGC() 73 | { 74 | Write("TemporaryDisableConcurrentGC"); 75 | } 76 | 77 | public bool IsConcurrentGCEnabled() 78 | { 79 | Write("IsConcurrentGCEnabled"); 80 | return false; 81 | } 82 | 83 | public HResult WaitUntilConcurrentGCCompleteAsync(int millisecondsTimeout) 84 | { 85 | Write("WaitUntilConcurrentGCCompleteAsync"); 86 | return default; 87 | } 88 | 89 | public nint GetNumberOfFinalizable() 90 | { 91 | Write("GetNumberOfFinalizable"); 92 | return 0; 93 | } 94 | 95 | public unsafe GCObject* GetNextFinalizable() 96 | { 97 | Write("GetNextFinalizable"); 98 | return null; 99 | } 100 | 101 | public uint GetMemoryLoad() 102 | { 103 | Write("GetMemoryLoad"); 104 | return 0; 105 | } 106 | 107 | public int GetGcLatencyMode() 108 | { 109 | Write("GetGcLatencyMode"); 110 | return 0; 111 | } 112 | 113 | public int SetGcLatencyMode(int newLatencyMode) 114 | { 115 | Write("SetGcLatencyMode"); 116 | return 0; 117 | } 118 | 119 | public int GetLOHCompactionMode() 120 | { 121 | Write("GetLOHCompactionMode"); 122 | return 0; 123 | } 124 | 125 | public void SetLOHCompactionMode(int newLOHCompactionMode) 126 | { 127 | Write("SetLOHCompactionMode"); 128 | } 129 | 130 | public bool RegisterForFullGCNotification(uint gen2Percentage, uint lohPercentage) 131 | { 132 | Write("RegisterForFullGCNotification"); 133 | return false; 134 | } 135 | 136 | public bool CancelFullGCNotification() 137 | { 138 | Write("CancelFullGCNotification"); 139 | return false; 140 | } 141 | 142 | public int WaitForFullGCApproach(int millisecondsTimeout) 143 | { 144 | Write("WaitForFullGCApproach"); 145 | return 0; 146 | } 147 | 148 | public int WaitForFullGCComplete(int millisecondsTimeout) 149 | { 150 | Write("WaitForFullGCComplete"); 151 | return 0; 152 | } 153 | 154 | public unsafe uint WhichGeneration(GCObject* obj) 155 | { 156 | Write("WhichGeneration"); 157 | return 0; 158 | } 159 | 160 | public int CollectionCount(int generation, int get_bgc_fgc_coutn) 161 | { 162 | Write("CollectionCount"); 163 | return 0; 164 | } 165 | 166 | public int StartNoGCRegion(ulong totalSize, bool lohSizeKnown, ulong lohSize, bool disallowFullBlockingGC) 167 | { 168 | Write("StartNoGCRegion"); 169 | return 0; 170 | } 171 | 172 | public int EndNoGCRegion() 173 | { 174 | Write("EndNoGCRegion"); 175 | return 0; 176 | } 177 | 178 | public nint GetTotalBytesInUse() 179 | { 180 | Write("GetTotalBytesInUse"); 181 | return 0; 182 | } 183 | 184 | public ulong GetTotalAllocatedBytes() 185 | { 186 | Write("GetTotalAllocatedBytes"); 187 | return 0; 188 | } 189 | 190 | public HResult GarbageCollect(int generation, bool low_memory_p, int mode) 191 | { 192 | Write("GarbageCollect"); 193 | 194 | _gcHandleManager.Store.DumpHandles(_dacManager); 195 | 196 | return HResult.S_OK; 197 | } 198 | 199 | public uint GetMaxGeneration() 200 | { 201 | Write("GetMaxGeneration"); 202 | return 2; 203 | } 204 | 205 | public unsafe void SetFinalizationRun(GCObject* obj) 206 | { 207 | Write("SetFinalizationRun"); 208 | } 209 | 210 | public unsafe bool RegisterForFinalization(int gen, GCObject* obj) 211 | { 212 | Write("RegisterForFinalization"); 213 | return false; 214 | } 215 | 216 | public int GetLastGCPercentTimeInGC() 217 | { 218 | Write("GetLastGCPercentTimeInGC"); 219 | return 0; 220 | } 221 | 222 | public nint GetLastGCGenerationSize(int gen) 223 | { 224 | Write("GetLastGCGenerationSize"); 225 | return 0; 226 | } 227 | 228 | public HResult Initialize() 229 | { 230 | Write("Initialize GCHeap"); 231 | 232 | if (DacManager.TryLoad(out var dacManager)) 233 | { 234 | _dacManager = dacManager; 235 | } 236 | 237 | var parameters = new WriteBarrierParameters(); 238 | 239 | parameters.operation = WriteBarrierOp.Initialize; 240 | parameters.is_runtime_suspended = true; 241 | parameters.requires_upper_bounds_check = false; // Actually ignored because Initialize 242 | //parameters.card_table = (uint*)Marshal.AllocHGlobal(sizeof(nint) * 2); 243 | parameters.ephemeral_low = (byte*)(~0); 244 | //parameters.ephemeral_high = (byte*)1; 245 | 246 | _gcToClr.StompWriteBarrier(¶meters); 247 | 248 | //parameters.operation = WriteBarrierOp.StompResize; 249 | 250 | //_gcToClr.StompWriteBarrier(Unsafe.AsPointer(ref parameters)); 251 | 252 | return HResult.S_OK; 253 | } 254 | 255 | public unsafe bool IsPromoted(GCObject* obj) 256 | { 257 | Write("IsPromoted"); 258 | return false; 259 | } 260 | 261 | public unsafe bool IsHeapPointer(IntPtr obj, bool small_heap_only) 262 | { 263 | Write("IsHeapPointer"); 264 | return false; 265 | } 266 | 267 | public uint GetCondemnedGeneration() 268 | { 269 | Write("GetCondemnedGeneration"); 270 | return 0; 271 | } 272 | 273 | public bool IsGCInProgressHelper(bool bConsiderGCStart = false) 274 | { 275 | Write("IsGCInProgressHelper"); 276 | return false; 277 | } 278 | 279 | public uint GetGcCount() 280 | { 281 | Write("GetGcCount"); 282 | return 0; 283 | } 284 | 285 | public unsafe bool IsThreadUsingAllocationContextHeap(gc_alloc_context* acontext, int thread_number) 286 | { 287 | Write("IsThreadUsingAllocationContextHeap"); 288 | return false; 289 | } 290 | 291 | public unsafe bool IsEphemeral(GCObject* obj) 292 | { 293 | Write("IsEphemeral"); 294 | return false; 295 | } 296 | 297 | public uint WaitUntilGCComplete(bool bConsiderGCStart = false) 298 | { 299 | Write("WaitUntilGCComplete"); 300 | return 0; 301 | } 302 | 303 | public unsafe void FixAllocContext(gc_alloc_context* acontext, void* arg, void* heap) 304 | { 305 | Write("FixAllocContext"); 306 | } 307 | 308 | public nint GetCurrentObjSize() 309 | { 310 | Write("GetCurrentObjSize"); 311 | return 0; 312 | } 313 | 314 | public void SetGCInProgress(bool fInProgress) 315 | { 316 | Write("SetGCInProgress"); 317 | } 318 | 319 | public bool RuntimeStructuresValid() 320 | { 321 | Write("RuntimeStructuresValid"); 322 | return false; 323 | } 324 | 325 | public void SetSuspensionPending(bool fSuspensionPending) 326 | { 327 | Write("SetSuspensionPending"); 328 | } 329 | 330 | public void SetYieldProcessorScalingFactor(float yieldProcessorScalingFactor) 331 | { 332 | Write("SetYieldProcessorScalingFactor"); 333 | } 334 | 335 | public void Shutdown() 336 | { 337 | Write("Shutdown"); 338 | } 339 | 340 | public nint GetLastGCStartTime(int generation) 341 | { 342 | Write("GetLastGCStartTime"); 343 | return 0; 344 | } 345 | 346 | public nint GetLastGCDuration(int generation) 347 | { 348 | Write("GetLastGCDuration"); 349 | return 0; 350 | } 351 | 352 | public nint GetNow() 353 | { 354 | Write("GetNow"); 355 | return 0; 356 | } 357 | 358 | public GCObject* Alloc(gc_alloc_context* acontext, nint size, uint flags) 359 | { 360 | Write($"Alloc: {size} (alloc context: {(IntPtr)acontext:x2}, start: {acontext->alloc_ptr:x2}, size: {size:x2}, limit: {acontext->alloc_limit:x2})"); 361 | 362 | var result = acontext->alloc_ptr; 363 | var advance = result + size; 364 | 365 | if (advance <= acontext->alloc_limit) 366 | { 367 | acontext->alloc_ptr = advance; 368 | return (GCObject*)result; 369 | } 370 | 371 | int beginGap = 24; 372 | int growthSize = 16 * 1024 * 1024; 373 | 374 | var newPages = (IntPtr)NativeMemory.AllocZeroed((nuint)growthSize); 375 | 376 | var allocationStart = newPages + beginGap; 377 | acontext->alloc_ptr = allocationStart + size; 378 | acontext->alloc_limit = newPages + growthSize; 379 | 380 | return (GCObject*)allocationStart; 381 | } 382 | 383 | public unsafe void PublishObject(IntPtr obj) 384 | { 385 | Write($"PublishObject: {obj:x2}"); 386 | } 387 | 388 | public void SetWaitForGCEvent() 389 | { 390 | Write("SetWaitForGCEvent"); 391 | } 392 | 393 | public void ResetWaitForGCEvent() 394 | { 395 | Write("ResetWaitForGCEvent"); 396 | } 397 | 398 | public unsafe bool IsLargeObject(GCObject* pObj) 399 | { 400 | Write("IsLargeObject"); 401 | return false; 402 | } 403 | 404 | public unsafe void ValidateObjectMember(GCObject* obj) 405 | { 406 | Write("ValidateObjectMember"); 407 | } 408 | 409 | public unsafe GCObject* NextObj(GCObject* obj) 410 | { 411 | Write("NextObj"); 412 | return null; 413 | } 414 | 415 | public unsafe GCObject* GetContainingObject(IntPtr pInteriorPtr, bool fCollectedGenOnly) 416 | { 417 | Write("GetContainingObject"); 418 | return null; 419 | } 420 | 421 | public unsafe void DiagWalkObject(GCObject* obj, void* fn, void* context) 422 | { 423 | Write("DiagWalkObject"); 424 | } 425 | 426 | public unsafe void DiagWalkObject2(GCObject* obj, void* fn, void* context) 427 | { 428 | Write("DiagWalkObject2"); 429 | } 430 | 431 | public unsafe void DiagWalkHeap(void* fn, void* context, int gen_number, bool walk_large_object_heap_p) 432 | { 433 | Write("DiagWalkHeap"); 434 | } 435 | 436 | public unsafe void DiagWalkSurvivorsWithType(void* gc_context, void* fn, void* diag_context, walk_surv_type type, int gen_number = -1) 437 | { 438 | Write("DiagWalkSurvivorsWithType"); 439 | } 440 | 441 | public unsafe void DiagWalkFinalizeQueue(void* gc_context, void* fn) 442 | { 443 | Write("DiagWalkFinalizeQueue"); 444 | } 445 | 446 | public unsafe void DiagScanFinalizeQueue(void* fn, void* context) 447 | { 448 | Write("DiagScanFinalizeQueue"); 449 | } 450 | 451 | public unsafe void DiagScanHandles(void* fn, int gen_number, void* context) 452 | { 453 | Write("DiagScanHandles"); 454 | } 455 | 456 | public unsafe void DiagScanDependentHandles(void* fn, int gen_number, void* context) 457 | { 458 | Write("DiagScanDependentHandles"); 459 | } 460 | 461 | public unsafe void DiagDescrGenerations(void* fn, void* context) 462 | { 463 | Write("DiagDescrGenerations"); 464 | } 465 | 466 | public void DiagTraceGCSegments() 467 | { 468 | Write("DiagTraceGCSegments"); 469 | } 470 | 471 | public unsafe void DiagGetGCSettings(void* settings) 472 | { 473 | Write("DiagGetGCSettings"); 474 | } 475 | 476 | public unsafe bool StressHeap(gc_alloc_context* acontext) 477 | { 478 | Write("StressHeap"); 479 | return false; 480 | } 481 | 482 | public unsafe void* RegisterFrozenSegment(segment_info* pseginfo) 483 | { 484 | Write("RegisterFrozenSegment"); 485 | return pseginfo; 486 | } 487 | 488 | public unsafe void UnregisterFrozenSegment(void* seg) 489 | { 490 | Write("UnregisterFrozenSegment"); 491 | } 492 | 493 | public unsafe bool IsInFrozenSegment(GCObject* obj) 494 | { 495 | Write("IsInFrozenSegment"); 496 | return false; 497 | } 498 | 499 | public void ControlEvents(GCEventKeyword keyword, GCEventLevel level) 500 | { 501 | Write("ControlEvents"); 502 | } 503 | 504 | public void ControlPrivateEvents(GCEventKeyword keyword, GCEventLevel level) 505 | { 506 | Write("ControlPrivateEvents"); 507 | } 508 | 509 | public unsafe uint GetGenerationWithRange(GCObject* obj, byte** ppStart, byte** ppAllocated, byte** ppReserved) 510 | { 511 | Write("GetGenerationWithRange"); 512 | return 0; 513 | } 514 | 515 | public long GetTotalPauseDuration() 516 | { 517 | Write("GetTotalPauseDuration"); 518 | return 0; 519 | } 520 | 521 | public void EnumerateConfigurationValues(void* context, nint configurationValueFunc) 522 | { 523 | Write("EnumerateConfigurationValues"); 524 | } 525 | 526 | public void UpdateFrozenSegment(nint seg, nint allocated, nint committed) 527 | { 528 | Write($"UpdateFrozenSegment {seg:x2} {allocated:x2} {committed:x2}"); 529 | } 530 | 531 | public int RefreshMemoryLimit() 532 | { 533 | Write("RefreshMemoryLimit"); 534 | return 0; 535 | } 536 | 537 | public enable_no_gc_region_callback_status EnableNoGCRegionCallback(nint callback, ulong callback_threshold) 538 | { 539 | Write("EnableNoGCRegionCallback"); 540 | return default; 541 | } 542 | 543 | public nint GetExtraWorkForFinalization() 544 | { 545 | Write("GetExtraWorkForFinalization"); 546 | return 0; 547 | } 548 | 549 | public ulong GetGenerationBudget(int generation) 550 | { 551 | Write("GetGenerationBudget"); 552 | return 0; 553 | } 554 | 555 | public nint GetLOHThreshold() 556 | { 557 | Write("GetLOHThreshold"); 558 | return 0; 559 | } 560 | 561 | public void DiagWalkHeapWithACHandling(nint fn, void* context, int gen_number, bool walk_large_object_heap_p) 562 | { 563 | Write("DiagWalkHeapWithACHandling"); 564 | } 565 | 566 | public void GetMemoryInfo(out ulong highMemLoadThresholdBytes, out ulong totalAvailableMemoryBytes, out ulong lastRecordedMemLoadBytes, out ulong lastRecordedHeapSizeBytes, out ulong lastRecordedFragmentationBytes, out ulong totalCommittedBytes, out ulong promotedBytes, out ulong pinnedObjectCount, out ulong finalizationPendingCount, out ulong index, out uint generation, out uint pauseTimePct, out bool isCompaction, out bool isConcurrent, out ulong genInfoRaw, out ulong pauseInfoRaw, int kind) 567 | { 568 | Write("GetMemoryInfo"); 569 | highMemLoadThresholdBytes = 0; 570 | totalAvailableMemoryBytes = 0; 571 | lastRecordedMemLoadBytes = 0; 572 | lastRecordedHeapSizeBytes = 0; 573 | lastRecordedFragmentationBytes = 0; 574 | totalCommittedBytes = 0; 575 | promotedBytes = 0; 576 | pinnedObjectCount = 0; 577 | finalizationPendingCount = 0; 578 | index = 0; 579 | generation = 0; 580 | pauseTimePct = 0; 581 | isCompaction = false; 582 | isConcurrent = false; 583 | genInfoRaw = 0; 584 | pauseInfoRaw = 0; 585 | } 586 | } 587 | -------------------------------------------------------------------------------- /ManagedDotnetGC/HResult.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC; 2 | 3 | public struct HResult 4 | { 5 | public const int S_OK = 0; 6 | public const int S_FALSE = 1; 7 | public const int E_FAIL = unchecked((int)0x80004005); 8 | public const int E_INVALIDARG = unchecked((int)0x80070057); 9 | public const int E_NOTIMPL = unchecked((int)0x80004001); 10 | public const int E_NOINTERFACE = unchecked((int)0x80004002); 11 | 12 | public bool IsOK => Value == S_OK; 13 | 14 | public int Value { get; set; } 15 | 16 | public HResult(int hr) => Value = hr; 17 | 18 | public static implicit operator HResult(int hr) => new HResult(hr); 19 | 20 | /// 21 | /// Helper to convert to int for comparisons. 22 | /// 23 | public static implicit operator int(HResult hr) => hr.Value; 24 | 25 | /// 26 | /// This makes "if (hr)" equivalent to SUCCEEDED(hr). 27 | /// 28 | public static implicit operator bool(HResult hr) => hr.Value >= 0; 29 | 30 | public override string ToString() 31 | { 32 | return Value switch 33 | { 34 | S_OK => "S_OK", 35 | S_FALSE => "S_FALSE", 36 | E_FAIL => "E_FAIL", 37 | E_INVALIDARG => "E_INVALIDARG", 38 | E_NOTIMPL => "E_NOTIMPL", 39 | E_NOINTERFACE => "E_NOINTERFACE", 40 | _ => $"{Value:x8}", 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ManagedDotnetGC/Interfaces/IGCHandleManager.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Interfaces; 2 | 3 | [NativeObject] 4 | public unsafe interface IGCHandleManager 5 | { 6 | bool Initialize(); 7 | 8 | void Shutdown(); 9 | 10 | nint GetGlobalHandleStore(); 11 | 12 | nint CreateHandleStore(); 13 | 14 | void DestroyHandleStore(nint store); 15 | 16 | OBJECTHANDLE CreateGlobalHandleOfType(GCObject* obj, HandleType type); 17 | 18 | OBJECTHANDLE CreateDuplicateHandle(OBJECTHANDLE handle); 19 | 20 | void DestroyHandleOfType(OBJECTHANDLE handle, HandleType type); 21 | 22 | void DestroyHandleOfUnknownType(OBJECTHANDLE handle); 23 | 24 | void SetExtraInfoForHandle(OBJECTHANDLE handle, HandleType type, void* pExtraInfo); 25 | 26 | void* GetExtraInfoFromHandle(OBJECTHANDLE handle); 27 | 28 | void StoreObjectInHandle(OBJECTHANDLE handle, GCObject* obj); 29 | 30 | bool StoreObjectInHandleIfNull(OBJECTHANDLE handle, GCObject* obj); 31 | 32 | void SetDependentHandleSecondary(OBJECTHANDLE handle, GCObject* obj); 33 | 34 | GCObject* GetDependentHandleSecondary(OBJECTHANDLE handle); 35 | 36 | GCObject* InterlockedCompareExchangeObjectInHandle(OBJECTHANDLE handle, GCObject* obj, GCObject* comparandObject); 37 | 38 | HandleType HandleFetchType(OBJECTHANDLE handle); 39 | 40 | void TraceRefCountedHandles(void* callback, uint* param1, uint* param2); 41 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/Interfaces/IGCHandleStore.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Interfaces; 2 | 3 | [NativeObject] 4 | public unsafe interface IGCHandleStore 5 | { 6 | void Uproot(); 7 | 8 | bool ContainsHandle(OBJECTHANDLE handle); 9 | 10 | OBJECTHANDLE CreateHandleOfType2(GCObject* obj, HandleType type, int heapToAffinitizeTo); 11 | 12 | OBJECTHANDLE CreateHandleOfType(GCObject* obj, HandleType type); 13 | 14 | OBJECTHANDLE CreateHandleWithExtraInfo(GCObject* obj, HandleType type, void* pExtraInfo); 15 | 16 | OBJECTHANDLE CreateDependentHandle(GCObject* primary, GCObject* secondary); 17 | 18 | void Destructor(); 19 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/Interfaces/IGCHeap.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Interfaces; 2 | 3 | [NativeObject] 4 | public unsafe interface IGCHeap 5 | { 6 | /* 7 | =========================================================================== 8 | Hosting APIs. These are used by GC hosting. The code that 9 | calls these methods may possibly be moved behind the interface - 10 | today, the VM handles the setting of segment size and max gen 0 size. 11 | (See src/vm/corehost.cpp) 12 | =========================================================================== 13 | */ 14 | 15 | // Returns whether or not the given size is a valid segment size. 16 | bool IsValidSegmentSize(nint size); 17 | 18 | // Returns whether or not the given size is a valid gen 0 max size. 19 | bool IsValidGen0MaxSize(nint size); 20 | 21 | // Gets a valid segment size. 22 | nint GetValidSegmentSize(bool large_seg = false); 23 | 24 | // Sets the limit for reserved memory. 25 | void SetReservedVMLimit(nint vmlimit); 26 | 27 | /* 28 | =========================================================================== 29 | Concurrent GC routines. These are used in various places in the VM 30 | to synchronize with the GC, when the VM wants to update something that 31 | the GC is potentially using, if it's doing a background GC. 32 | 33 | Concrete examples of this are profiling/ETW scenarios. 34 | =========================================================================== 35 | */ 36 | 37 | // Blocks until any running concurrent GCs complete. 38 | void WaitUntilConcurrentGCComplete(); 39 | 40 | // Returns true if a concurrent GC is in progress, false otherwise. 41 | bool IsConcurrentGCInProgress(); 42 | 43 | // Temporarily enables concurrent GC, used during profiling. 44 | void TemporaryEnableConcurrentGC(); 45 | 46 | // Temporarily disables concurrent GC, used during profiling. 47 | void TemporaryDisableConcurrentGC(); 48 | 49 | // Returns whether or not Concurrent GC is enabled. 50 | bool IsConcurrentGCEnabled(); 51 | 52 | // Wait for a concurrent GC to complete if one is in progress, with the given timeout. 53 | HResult WaitUntilConcurrentGCCompleteAsync(int millisecondsTimeout); // Use in native threads. TRUE if succeed. FALSE if failed or timeout 54 | 55 | 56 | /* 57 | =========================================================================== 58 | Finalization routines. These are used by the finalizer thread to communicate 59 | with the GC. 60 | =========================================================================== 61 | */ 62 | 63 | // Gets the number of finalizable objects. 64 | nint GetNumberOfFinalizable(); 65 | 66 | // Gets the next finalizable object. 67 | GCObject* GetNextFinalizable(); 68 | 69 | /* 70 | =========================================================================== 71 | BCL routines. These are routines that are directly exposed by CoreLib 72 | as a part of the `System.GC` class. These routines behave in the same 73 | manner as the functions on `System.GC`. 74 | =========================================================================== 75 | */ 76 | 77 | // Gets memory related information the last GC observed. Depending on the last arg, this could 78 | // be any last GC that got recorded, or of the kind specified by this arg. All info below is 79 | // what was observed by that last GC. 80 | // 81 | // highMemLoadThreshold - physical memory load (in percentage) when GC will start to 82 | // react aggressively to reclaim memory. 83 | // totalPhysicalMem - the total amount of phyiscal memory available on the machine and the memory 84 | // limit set on the container if running in a container. 85 | // lastRecordedMemLoad - physical memory load in percentage. 86 | // lastRecordedHeapSizeBytes - total managed heap size. 87 | // lastRecordedFragmentation - total fragmentation in the managed heap. 88 | // totalCommittedBytes - total committed bytes by the managed heap. 89 | // promotedBytes - promoted bytes. 90 | // pinnedObjectCount - # of pinned objects observed. 91 | // finalizationPendingCount - # of objects ready for finalization. 92 | // index - the index of the GC. 93 | // generation - the generation the GC collected. 94 | // pauseTimePct - the % pause time in GC so far since process started. 95 | // isCompaction - compacted or not. 96 | // isConcurrent - concurrent or not. 97 | // genInfoRaw - info about each generation. 98 | // pauseInfoRaw - pause info. 99 | void GetMemoryInfo(out ulong highMemLoadThresholdBytes, 100 | out ulong totalAvailableMemoryBytes, 101 | out ulong lastRecordedMemLoadBytes, 102 | out ulong lastRecordedHeapSizeBytes, 103 | out ulong lastRecordedFragmentationBytes, 104 | out ulong totalCommittedBytes, 105 | out ulong promotedBytes, 106 | out ulong pinnedObjectCount, 107 | out ulong finalizationPendingCount, 108 | out ulong index, 109 | out uint generation, 110 | out uint pauseTimePct, 111 | out bool isCompaction, 112 | out bool isConcurrent, 113 | out ulong genInfoRaw, 114 | out ulong pauseInfoRaw, 115 | int kind); 116 | 117 | // Get the last memory load in percentage observed by the last GC. 118 | uint GetMemoryLoad(); 119 | 120 | // Gets the current GC latency mode. 121 | int GetGcLatencyMode(); 122 | 123 | // Sets the current GC latency mode. newLatencyMode has already been 124 | // verified by CoreLib to be valid. 125 | int SetGcLatencyMode(int newLatencyMode); 126 | 127 | // Gets the current LOH compaction mode. 128 | int GetLOHCompactionMode(); 129 | 130 | // Sets the current LOH compaction mode. newLOHCompactionMode has 131 | // already been verified by CoreLib to be valid. 132 | void SetLOHCompactionMode(int newLOHCompactionMode); 133 | 134 | // Registers for a full GC notification, raising a notification if the gen 2 or 135 | // LOH object heap thresholds are exceeded. 136 | bool RegisterForFullGCNotification(uint gen2Percentage, uint lohPercentage); 137 | 138 | // Cancels a full GC notification that was requested by `RegisterForFullGCNotification`. 139 | bool CancelFullGCNotification(); 140 | 141 | // Returns the status of a registered notification for determining whether a blocking 142 | // Gen 2 collection is about to be initiated, with the given timeout. 143 | int WaitForFullGCApproach(int millisecondsTimeout); 144 | 145 | // Returns the status of a registered notification for determining whether a blocking 146 | // Gen 2 collection has completed, with the given timeout. 147 | int WaitForFullGCComplete(int millisecondsTimeout); 148 | 149 | // Returns the generation in which obj is found. Also used by the VM 150 | // in some places, in particular syncblk code. 151 | uint WhichGeneration(GCObject* obj); 152 | 153 | // Returns the number of GCs that have transpired in the given generation 154 | // since the beginning of the life of the process. Also used by the VM 155 | // for debug code. 156 | int CollectionCount(int generation, int get_bgc_fgc_coutn); 157 | 158 | // Begins a no-GC region, returning a code indicating whether entering the no-GC 159 | // region was successful. 160 | int StartNoGCRegion(ulong totalSize, bool lohSizeKnown, ulong lohSize, bool disallowFullBlockingGC); 161 | 162 | // Exits a no-GC region. 163 | int EndNoGCRegion(); 164 | 165 | // Gets the total number of bytes in use. 166 | nint GetTotalBytesInUse(); 167 | 168 | ulong GetTotalAllocatedBytes(); 169 | 170 | // Forces a garbage collection of the given generation. Also used extensively 171 | // throughout the VM. 172 | HResult GarbageCollect(int generation, bool low_memory_p, int mode); 173 | 174 | // Gets the largest GC generation. Also used extensively throughout the VM. 175 | uint GetMaxGeneration(); 176 | 177 | // Indicates that an object's finalizer should not be run upon the object's collection. 178 | void SetFinalizationRun(GCObject* obj); 179 | 180 | // Indicates that an object's finalizer should be run upon the object's collection. 181 | bool RegisterForFinalization(int gen, GCObject* obj); 182 | 183 | int GetLastGCPercentTimeInGC(); 184 | 185 | nint GetLastGCGenerationSize(int gen); 186 | 187 | /* 188 | =========================================================================== 189 | Miscellaneous routines used by the VM. 190 | =========================================================================== 191 | */ 192 | 193 | // Initializes the GC heap, returning whether or not the initialization 194 | // was successful. 195 | HResult Initialize(); 196 | 197 | // Returns whether nor this GC was promoted by the last GC. 198 | bool IsPromoted(GCObject* obj); 199 | 200 | // Returns true if this pointer points into a GC heap, false otherwise. 201 | bool IsHeapPointer(IntPtr obj, bool small_heap_only); 202 | 203 | // Return the generation that has been condemned by the current GC. 204 | uint GetCondemnedGeneration(); 205 | 206 | // Returns whether or not a GC is in progress. 207 | bool IsGCInProgressHelper(bool bConsiderGCStart = false); 208 | 209 | // Returns the number of GCs that have occured. Mainly used for 210 | // sanity checks asserting that a GC has not occured. 211 | uint GetGcCount(); 212 | 213 | // Gets whether or not the home heap of this alloc context matches the heap 214 | // associated with this thread. 215 | bool IsThreadUsingAllocationContextHeap(gc_alloc_context* acontext, int thread_number); 216 | 217 | // Returns whether or not this object resides in an ephemeral generation. 218 | bool IsEphemeral(GCObject* obj); 219 | 220 | // Blocks until a GC is complete, returning a code indicating the wait was successful. 221 | uint WaitUntilGCComplete(bool bConsiderGCStart = false); 222 | 223 | // "Fixes" an allocation context by binding its allocation pointer to a 224 | // location on the heap. 225 | void FixAllocContext(gc_alloc_context* acontext, void* arg, void* heap); 226 | 227 | // Gets the total survived size plus the total allocated bytes on the heap. 228 | nint GetCurrentObjSize(); 229 | 230 | // Sets whether or not a GC is in progress. 231 | void SetGCInProgress(bool fInProgress); 232 | 233 | // Gets whether or not the GC runtime structures are in a valid state for heap traversal. 234 | bool RuntimeStructuresValid(); 235 | 236 | // Tells the GC when the VM is suspending threads. 237 | void SetSuspensionPending(bool fSuspensionPending); 238 | 239 | // Tells the GC how many YieldProcessor calls are equal to one scaled yield processor call. 240 | void SetYieldProcessorScalingFactor(float yieldProcessorScalingFactor); 241 | 242 | // Flush the log and close the file if GCLog is turned on. 243 | void Shutdown(); 244 | 245 | /* 246 | ============================================================================ 247 | Add/RemoveMemoryPressure support routines. These are on the interface 248 | for now, but we should move Add/RemoveMemoryPressure from the VM to the GC. 249 | When that occurs, these three routines can be removed from the interface. 250 | ============================================================================ 251 | */ 252 | 253 | // Get the timestamp corresponding to the last GC that occured for the 254 | // given generation. 255 | nint GetLastGCStartTime(int generation); 256 | 257 | // Gets the duration of the last GC that occured for the given generation. 258 | nint GetLastGCDuration(int generation); 259 | 260 | // Gets a timestamp for the current moment in time. 261 | nint GetNow(); 262 | 263 | /* 264 | =========================================================================== 265 | Allocation routines. These all call into the GC's allocator and may trigger a garbage 266 | collection. All allocation routines return NULL when the allocation request 267 | couldn't be serviced due to being out of memory. 268 | =========================================================================== 269 | */ 270 | 271 | // Allocates an object on the given allocation context with the given size and flags. 272 | // It is the responsibility of the caller to ensure that the passed-in alloc context is 273 | // owned by the thread that is calling this function. If using per-thread alloc contexts, 274 | // no lock is needed; callers not using per-thread alloc contexts will need to acquire 275 | // a lock to ensure that the calling thread has unique ownership over this alloc context; 276 | GCObject* Alloc(gc_alloc_context* acontext, nint size, uint flags); 277 | 278 | // This is for the allocator to indicate it's done allocating a large object during a 279 | // background GC as the BGC threads also need to walk UOH. 280 | void PublishObject(IntPtr obj); 281 | 282 | // Signals the WaitForGCEvent event, indicating that a GC has completed. 283 | void SetWaitForGCEvent(); 284 | 285 | // Resets the state of the WaitForGCEvent back to an unsignalled state. 286 | void ResetWaitForGCEvent(); 287 | 288 | /* 289 | =========================================================================== 290 | Heap verification routines. These are used during heap verification only. 291 | =========================================================================== 292 | */ 293 | // Returns whether or not this object is too large for SOH. 294 | bool IsLargeObject(GCObject* pObj); 295 | 296 | // Walks an object and validates its members. 297 | void ValidateObjectMember(GCObject* obj); 298 | 299 | // Retrieves the next object after the given object. When the EE 300 | // is not suspended, the result is not accurate - if the input argument 301 | // is in Gen0, the function could return zeroed out memory as the next object. 302 | GCObject* NextObj(GCObject* obj); 303 | 304 | // Given an interior pointer, return a pointer to the object 305 | // containing that pointer. This is safe to call only when the EE is suspended. 306 | // When fCollectedGenOnly is true, it only returns the object if it's found in 307 | // the generation(s) that are being collected. 308 | GCObject* GetContainingObject(IntPtr pInteriorPtr, bool fCollectedGenOnly); 309 | 310 | /* 311 | =========================================================================== 312 | Profiling routines. Used for event tracing and profiling to broadcast 313 | information regarding the heap. 314 | =========================================================================== 315 | */ 316 | 317 | // Walks an object, invoking a callback on each member. 318 | void DiagWalkObject(GCObject* obj, void* fn, void* context); 319 | //void DiagWalkObject(GCObject* obj, delegate* unmanaged[Stdcall] fn, void* context); 320 | 321 | // Walks an object, invoking a callback on each member. 322 | void DiagWalkObject2(GCObject* obj, void* fn, void* context); 323 | //void DiagWalkObject2(GCObject* obj, delegate* unmanaged[Stdcall] fn, void* context); 324 | 325 | // Walk the heap object by object. 326 | void DiagWalkHeap(void* fn, void* context, int gen_number, bool walk_large_object_heap_p); 327 | //void DiagWalkHeap(delegate* unmanaged[Stdcall] fn, void* context, int gen_number, bool walk_large_object_heap_p); 328 | 329 | // Walks the survivors and get the relocation information if objects have moved. 330 | // gen_number is used when type == walk_for_uoh, otherwise ignored 331 | void DiagWalkSurvivorsWithType(void* gc_context, void* fn, void* diag_context, walk_surv_type type, int gen_number = -1); 332 | 333 | // Walks the finalization queue. 334 | void DiagWalkFinalizeQueue(void* gc_context, void* fn); 335 | 336 | // Scan roots on finalizer queue. This is a generic function. 337 | void DiagScanFinalizeQueue(void* fn, void* context); 338 | 339 | // Scan handles for profiling or ETW. 340 | void DiagScanHandles(void* fn, int gen_number, void* context); 341 | 342 | // Scan dependent handles for profiling or ETW. 343 | void DiagScanDependentHandles(void* fn, int gen_number, void* context); 344 | 345 | // Describes all generations to the profiler, invoking a callback on each generation. 346 | void DiagDescrGenerations(void* fn, void* context); 347 | 348 | // Traces all GC segments and fires ETW events with information on them. 349 | void DiagTraceGCSegments(); 350 | 351 | // Get GC settings for tracing purposes. These are settings not obvious from a trace. 352 | void DiagGetGCSettings(void* settings); 353 | 354 | /* 355 | =========================================================================== 356 | GC Stress routines. Used only when running under GC Stress. 357 | =========================================================================== 358 | */ 359 | 360 | // Returns TRUE if GC actually happens, otherwise FALSE. The passed alloc context 361 | // must not be null. 362 | bool StressHeap(gc_alloc_context* acontext); 363 | 364 | /* 365 | =========================================================================== 366 | Routines to register read only segments for frozen objects. 367 | Only valid if FEATURE_BASICFREEZE is defined. 368 | =========================================================================== 369 | */ 370 | 371 | // Registers a frozen segment with the GC. 372 | void* RegisterFrozenSegment(segment_info* pseginfo); 373 | 374 | // Unregisters a frozen segment. 375 | void UnregisterFrozenSegment(void* seg); 376 | 377 | // Indicates whether an object is in a frozen segment. 378 | bool IsInFrozenSegment(GCObject* obj); 379 | 380 | /* 381 | =========================================================================== 382 | Routines for informing the GC about which events are enabled. 383 | =========================================================================== 384 | */ 385 | 386 | // Enables or disables the given keyword or level on the default event provider. 387 | void ControlEvents(GCEventKeyword keyword, GCEventLevel level); 388 | 389 | // Enables or disables the given keyword or level on the private event provider. 390 | void ControlPrivateEvents(GCEventKeyword keyword, GCEventLevel level); 391 | 392 | uint GetGenerationWithRange(GCObject* obj, byte** ppStart, byte** ppAllocated, byte** ppReserved); 393 | 394 | // Get the total paused duration. 395 | long GetTotalPauseDuration(); 396 | 397 | // Gets all the names and values of the GC configurations. 398 | void EnumerateConfigurationValues(void* context, IntPtr configurationValueFunc); 399 | 400 | // Updates given frozen segment 401 | void UpdateFrozenSegment(IntPtr seg, IntPtr allocated, IntPtr committed); 402 | 403 | // Refresh the memory limit 404 | int RefreshMemoryLimit(); 405 | 406 | // Enable NoGCRegionCallback 407 | enable_no_gc_region_callback_status EnableNoGCRegionCallback(IntPtr callback, ulong callback_threshold); 408 | 409 | // Get extra work for the finalizer 410 | IntPtr GetExtraWorkForFinalization(); 411 | 412 | ulong GetGenerationBudget(int generation); 413 | 414 | nint GetLOHThreshold(); 415 | 416 | // Walk the heap object by object outside of a GC. 417 | void DiagWalkHeapWithACHandling(IntPtr fn, void* context, int gen_number, bool walk_large_object_heap_p); 418 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/Interfaces/IGCToCLR.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Interfaces; 2 | 3 | [NativeObject] 4 | public unsafe interface IGCToCLR 5 | { 6 | // Suspends the EE for the given reason. 7 | void SuspendEE(SUSPEND_REASON reason); 8 | 9 | // Resumes all paused threads, with a boolean indicating 10 | // if the EE is being restarted because a GC is complete. 11 | void RestartEE(bool bFinishedGC); 12 | 13 | // Performs a stack walk of all managed threads and invokes the given promote_func 14 | // on all GC roots encountered on the stack. Depending on the condemned generation, 15 | // this function may also enumerate all static GC refs if necessary. 16 | void GcScanRoots(void* fn, int condemned, int max_gen, void* sc); 17 | 18 | // Callback from the GC informing the EE that it is preparing to start working. 19 | void GcStartWork(int condemned, int max_gen); 20 | 21 | // Callback from the GC informing the EE that the scanning of roots is about 22 | // to begin. 23 | void BeforeGcScanRoots(int condemned, bool is_bgc, bool is_concurrent); 24 | 25 | // Callback from the GC informing the EE that it has completed the managed stack 26 | // scan. User threads are still suspended at this point. 27 | void AfterGcScanRoots(int condemned, int max_gen, void* sc); 28 | 29 | // Callback from the GC informing the EE that a GC has completed. 30 | void GcDone(int condemned); 31 | 32 | // Predicate for the GC to query whether or not a given refcounted handle should 33 | // be promoted. 34 | bool RefCountedHandleCallbacks(GCObject* pObject); 35 | 36 | // Performs a weak pointer scan of the sync block cache. 37 | void SyncBlockCacheWeakPtrScan(void* scanProc, nint lp1, nint lp2); 38 | 39 | // Indicates to the EE that the GC intends to demote objects in the sync block cache. 40 | 41 | void SyncBlockCacheDemote(int max_gen); 42 | 43 | // Indicates to the EE that the GC has granted promotion to objects in the sync block cache. 44 | void SyncBlockCachePromotionsGranted(int max_gen); 45 | 46 | uint GetActiveSyncBlockCount(); 47 | 48 | // Queries whether or not the current thread has preemptive GC disabled. 49 | bool IsPreemptiveGCDisabled(); 50 | 51 | // Enables preemptive GC on the current thread. Returns true if the thread mode 52 | // was changed and false if the thread mode wasn't changed or the thread is not 53 | // a managed thread. 54 | bool EnablePreemptiveGC(); 55 | 56 | // Disables preemptive GC on the current thread. 57 | void DisablePreemptiveGC(); 58 | 59 | // Gets the Thread instance for the current thread, or null if no thread 60 | // instance is associated with this thread. 61 | // 62 | // If the GC created the current thread, GetThread returns null for threads 63 | // that were not created as suspendable (see `IGCHeap::CreateThread`). 64 | 65 | void* GetThread(); 66 | 67 | // Retrieves the alloc context associated with the current thread. 68 | 69 | gc_alloc_context* GetAllocContext(); 70 | 71 | // Calls the given enum_alloc_context_func with every active alloc context. 72 | 73 | void GcEnumAllocContexts(void* fn, void* param); 74 | 75 | // Get the Allocator for objects from collectible assemblies 76 | 77 | byte* GetLoaderAllocatorObjectForGC(GCObject* pObject); 78 | 79 | // Creates and returns a new thread. 80 | // Parameters: 81 | // threadStart - The function that will serve as the thread stub for the 82 | // new thread. It will be invoked immediately upon the 83 | // new thread upon creation. 84 | // arg - The argument that will be passed verbatim to threadStart. 85 | // is_suspendable - Whether or not the thread that is created should be suspendable 86 | // from a runtime perspective. Threads that are suspendable have 87 | // a VM Thread object associated with them that can be accessed 88 | // using `IGCHeap::GetThread`. 89 | // name - The name of this thread, optionally used for diagnostic purposes. 90 | // Returns: 91 | // true if the thread was started successfully, false if not. 92 | bool CreateThread(void* threadStart, void* arg, bool is_suspendable, char* name); 93 | 94 | // When a GC starts, gives the diagnostics code a chance to run. 95 | void DiagGCStart(int gen, bool isInduced); 96 | 97 | // When GC heap segments change, gives the diagnostics code a chance to run. 98 | void DiagUpdateGenerationBounds(); 99 | 100 | // When a GC ends, gives the diagnostics code a chance to run. 101 | void DiagGCEnd(nint index, int gen, int reason, bool fConcurrent); 102 | 103 | // During a GC after we discover what objects' finalizers should run, gives the diagnostics code a chance to run. 104 | void DiagWalkFReachableObjects(void* gcContext); 105 | 106 | // During a GC after we discover the survivors and the relocation info, 107 | // gives the diagnostics code a chance to run. This includes LOH if we are 108 | // compacting LOH. 109 | void DiagWalkSurvivors(void* gcContext, bool fCompacting); 110 | 111 | // During a full GC after we discover what objects to survive on UOH, 112 | // gives the diagnostics code a chance to run. 113 | void DiagWalkUOHSurvivors(void* gcContext, int gen); 114 | 115 | // At the end of a background GC, gives the diagnostics code a chance to run. 116 | void DiagWalkBGCSurvivors(void* gcContext); 117 | 118 | // Informs the EE of changes to the location of the card table, potentially updating the write 119 | // barrier if it needs to be updated. 120 | void StompWriteBarrier(WriteBarrierParameters* args); 121 | 122 | // Signals to the finalizer thread that there are objects ready to 123 | // be finalized. 124 | void EnableFinalization(bool foundFinalizers); 125 | 126 | // Signals to the EE that the GC encountered a fatal error and can't recover. 127 | void HandleFatalError(uint exitCode); 128 | 129 | // Offers the EE the option to finalize the given object eagerly, i.e. 130 | // not on the finalizer thread but on the current thread. The 131 | // EE returns true if it finalized the object eagerly and the GC does not 132 | // need to do so, and false if it chose not to eagerly finalize the object 133 | // and it's up to the GC to finalize it later. 134 | bool EagerFinalized(GCObject* obj); 135 | 136 | // Retrieves the method table for the free object, a special kind of object used by the GC 137 | // to keep the heap traversable. Conceptually, the free object is similar to a managed array 138 | // of bytes: it consists of an object header (like all objects) and a "numComponents" field, 139 | // followed by some number of bytes of space that's free on the heap. 140 | // 141 | // The free object allows the GC to traverse the heap because it can inspect the numComponents 142 | // field to see how many bytes to skip before the next object on a heap segment begins. 143 | void* GetFreeObjectMethodTable(); 144 | 145 | // Asks the EE for the value of a given configuration key. If the EE does not know or does not 146 | // have a value for the requeested config key, false is returned and the value of the passed-in 147 | // pointer is undefined. Otherwise, true is returned and the config key's value is written to 148 | // the passed-in pointer. 149 | bool GetBooleanConfigValue(char* privateKey, char* publicKey, bool* value); 150 | 151 | bool GetIntConfigValue(char* privateKey, char* publicKey, long* value); 152 | 153 | bool GetStringConfigValue(char* privateKey, char* publicKey, char** value); 154 | 155 | void FreeStringConfigValue(char* value); 156 | 157 | // Returns true if this thread is a "GC thread", or a thread capable of 158 | // doing GC work. Threads are either /always/ GC threads 159 | // (if they were created for this purpose - background GC threads 160 | // and server GC threads) or they became GC threads by suspending the EE 161 | // and initiating a collection. 162 | bool IsGCThread(); 163 | 164 | // Returns true if the current thread is either a background GC thread 165 | // or a server GC thread. 166 | bool WasCurrentThreadCreatedByGC(); 167 | 168 | // Given an object, if this object is an instance of `System.Threading.OverlappedData`, 169 | // and the runtime treats instances of this class specially, traverses the objects that 170 | // are directly or (once) indirectly pinned by this object and reports them to the GC for 171 | // the purposes of relocation and promotion. 172 | // 173 | // Overlapped objects are very special and as such the objects they wrap can't be promoted in 174 | // the same manner as normal objects. This callback gives the EE the opportunity to hide these 175 | // details, if they are implemented at all. 176 | // 177 | // This function is a no-op if "object" is not an OverlappedData object. 178 | void WalkAsyncPinnedForPromotion(GCObject* obj, void* sc, void* callback); 179 | 180 | // Given an object, if this object is an instance of `System.Threading.OverlappedData` and the 181 | // runtime treats instances of this class specially, traverses the objects that are directly 182 | // or once indirectly pinned by this object and invokes the given callback on them. The callback 183 | // is passed the following arguments: 184 | // Object* "from" - The object that "caused" the "to" object to be pinned. If a single object 185 | // is pinned directly by this OverlappedData, this object will be the 186 | // OverlappedData object itself. If an array is pinned by this OverlappedData, 187 | // this object will be the pinned array. 188 | // Object* "to" - The object that is pinned by the "from" object. If a single object is pinned 189 | // by an OverlappedData, "to" will be that single object. If an array is pinned 190 | // by an OverlappedData, the callback will be invoked on all elements of that 191 | // array and each element will be a "to" object. 192 | // void* "context" - Passed verbatim from "WalkOverlappedObject" to the callback function. 193 | // The "context" argument will be passed directly to the callback without modification or inspection. 194 | // 195 | // This function is a no-op if "object" is not an OverlappedData object. 196 | void WalkAsyncPinned(GCObject* obj, void* context, void* func); 197 | 198 | // Returns an IGCToCLREventSink instance that can be used to fire events. 199 | void* EventSink(); 200 | 201 | uint GetTotalNumSizedRefHandles(); 202 | 203 | bool AnalyzeSurvivorsRequested(int condemnedGeneration); 204 | 205 | void AnalyzeSurvivorsFinished(nint gcIndex, int condemnedGeneration, ulong promoted_bytes, void* reportGenerationBounds); 206 | 207 | void VerifySyncTableEntry(); 208 | 209 | void UpdateGCEventStatus(int publicLevel, int publicKeywords, int privateLEvel, int privateKeywords); 210 | 211 | void LogStressMsg(uint level, uint facility, nint msg); 212 | 213 | uint GetCurrentProcessCpuCount(); 214 | 215 | void DiagAddNewRegion(int generation, byte* rangeStart, byte* rangeEnd, byte* rangeEndReserved); 216 | 217 | // The following method is available only with EE_INTERFACE_MAJOR_VERSION >= 1 218 | void LogErrorToHost(byte* message); 219 | 220 | ulong GetThreadOSThreadId(IntPtr thread); 221 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/Interfaces/IUnknown.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC.Interfaces; 2 | 3 | [NativeObject] 4 | public interface IUnknown 5 | { 6 | HResult QueryInterface(in Guid guid, out nint ptr); 7 | int AddRef(); 8 | int Release(); 9 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/Log.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedDotnetGC; 2 | 3 | internal class Log 4 | { 5 | public static void Write(string str) 6 | { 7 | Console.WriteLine($"[GC] {str}"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ManagedDotnetGC/ManagedDotnetGC.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | true 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ManagedDotnetGC/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ManagedDotnetGC": { 4 | "commandName": "Project", 5 | "commandLineArgs": "E:\\git\\ManagedDotnetGC\\TestApp\\bin\\Debug\\net6.0\\win-x64\\TestApp.exe", 6 | "workingDirectory": "E:\\git\\ManagedDotnetGC\\TestApp\\bin\\Debug\\net6.0\\win-x64", 7 | "environmentVariables": { 8 | "COMPlus_GCName": "ManagedDotnetGC.dll" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ManagedDotnetGC/Types.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ManagedDotnetGC; 5 | 6 | [StructLayout(LayoutKind.Sequential)] 7 | public readonly struct GcDacVars 8 | { 9 | public readonly byte Major_version_number; 10 | public readonly byte Minor_version_number; 11 | public readonly nint Generation_size; 12 | public readonly nint Total_generation_count; 13 | } 14 | 15 | [StructLayout(LayoutKind.Sequential)] 16 | public unsafe struct VersionInfo 17 | { 18 | public int MajorVersion; 19 | public int MinorVersion; 20 | public int BuildVersion; 21 | public byte* Name; 22 | } 23 | 24 | // SUSPEND_REASON is the reason why the GC wishes to suspend the EE, 25 | // used as an argument to IGCToCLR::SuspendEE. 26 | public enum SUSPEND_REASON 27 | { 28 | SUSPEND_FOR_GC = 1, 29 | SUSPEND_FOR_GC_PREP = 6 30 | } 31 | 32 | public readonly struct GCObject 33 | { 34 | public readonly IntPtr MethodTable; 35 | public readonly int Length; 36 | } 37 | 38 | public unsafe struct gc_alloc_context 39 | { 40 | public nint alloc_ptr; 41 | public nint alloc_limit; 42 | public long alloc_bytes; //Number of bytes allocated on SOH by this context 43 | 44 | public long alloc_bytes_uoh; //Number of bytes allocated not on SOH by this context 45 | 46 | // These two fields are deliberately not exposed past the EE-GC interface. 47 | public void* gc_reserved_1; 48 | public void* gc_reserved_2; 49 | public int alloc_count; 50 | } 51 | 52 | public enum enable_no_gc_region_callback_status 53 | { 54 | succeed, 55 | not_started, 56 | insufficient_budget, 57 | already_registered, 58 | }; 59 | 60 | public enum walk_surv_type 61 | { 62 | walk_for_gc = 1, 63 | walk_for_bgc = 2, 64 | walk_for_uoh = 3 65 | } 66 | 67 | public unsafe struct segment_info 68 | { 69 | public void* pvMem; // base of the allocation, not the first object (must add ibFirstObject) 70 | public nint ibFirstObject; // offset to the base of the first object in the segment 71 | public nint ibAllocated; // limit of allocated memory in the segment (>= firstobject) 72 | public nint ibCommit; // limit of committed memory in the segment (>= allocated) 73 | public nint ibReserved; // limit of reserved memory in the segment (>= commit) 74 | } 75 | 76 | // Event keywords corresponding to events that can be fired by the GC. These 77 | // numbers come from the ETW manifest itself - please make changes to this enum 78 | // if you add, remove, or change keyword sets that are used by the GC! 79 | [Flags] 80 | public enum GCEventKeyword 81 | { 82 | GCEventKeyword_None = 0x0, 83 | GCEventKeyword_GC = 0x1, 84 | // Duplicate on purpose, GCPrivate is the same keyword as GC, 85 | // with a different provider 86 | GCEventKeyword_GCPrivate = 0x1, 87 | GCEventKeyword_GCHandle = 0x2, 88 | GCEventKeyword_GCHandlePrivate = 0x4000, 89 | GCEventKeyword_GCHeapDump = 0x100000, 90 | GCEventKeyword_GCSampledObjectAllocationHigh = 0x200000, 91 | GCEventKeyword_GCHeapSurvivalAndMovement = 0x400000, 92 | GCEventKeyword_GCHeapCollect = 0x800000, 93 | GCEventKeyword_GCHeapAndTypeNames = 0x1000000, 94 | GCEventKeyword_GCSampledObjectAllocationLow = 0x2000000, 95 | GCEventKeyword_All = GCEventKeyword_GC 96 | | GCEventKeyword_GCPrivate 97 | | GCEventKeyword_GCHandle 98 | | GCEventKeyword_GCHandlePrivate 99 | | GCEventKeyword_GCHeapDump 100 | | GCEventKeyword_GCSampledObjectAllocationHigh 101 | | GCEventKeyword_GCHeapSurvivalAndMovement 102 | | GCEventKeyword_GCHeapCollect 103 | | GCEventKeyword_GCHeapAndTypeNames 104 | | GCEventKeyword_GCSampledObjectAllocationLow 105 | } 106 | 107 | // Event levels corresponding to events that can be fired by the GC. 108 | public enum GCEventLevel 109 | { 110 | GCEventLevel_None = 0, 111 | GCEventLevel_Fatal = 1, 112 | GCEventLevel_Error = 2, 113 | GCEventLevel_Warning = 3, 114 | GCEventLevel_Information = 4, 115 | GCEventLevel_Verbose = 5, 116 | GCEventLevel_Max = 6, 117 | GCEventLevel_LogAlways = 255 118 | } 119 | 120 | public unsafe struct OBJECTHANDLE 121 | { 122 | public OBJECTHANDLE(nint address) 123 | { 124 | Address = address; 125 | } 126 | 127 | public nint Address; 128 | 129 | public void SetObject(nint value) 130 | { 131 | *(nint*)Address = value; 132 | } 133 | 134 | public override string ToString() => $"{Address:x2}"; 135 | } 136 | 137 | public enum HandleType 138 | { 139 | /* 140 | * WEAK HANDLES 141 | * 142 | * Weak handles are handles that track an object as long as it is alive, 143 | * but do not keep the object alive if there are no strong references to it. 144 | * 145 | */ 146 | 147 | /* 148 | * SHORT-LIVED WEAK HANDLES 149 | * 150 | * Short-lived weak handles are weak handles that track an object until the 151 | * first time it is detected to be unreachable. At this point, the handle is 152 | * severed, even if the object will be visible from a pending finalization 153 | * graph. This further implies that short weak handles do not track 154 | * across object resurrections. 155 | * 156 | */ 157 | HNDTYPE_WEAK_SHORT = 0, 158 | 159 | /* 160 | * LONG-LIVED WEAK HANDLES 161 | * 162 | * Long-lived weak handles are weak handles that track an object until the 163 | * object is actually reclaimed. Unlike short weak handles, long weak handles 164 | * continue to track their referents through finalization and across any 165 | * resurrections that may occur. 166 | * 167 | */ 168 | HNDTYPE_WEAK_LONG = 1, 169 | HNDTYPE_WEAK_DEFAULT = 1, 170 | 171 | /* 172 | * STRONG HANDLES 173 | * 174 | * Strong handles are handles which function like a normal object reference. 175 | * The existence of a strong handle for an object will cause the object to 176 | * be promoted (remain alive) through a garbage collection cycle. 177 | * 178 | */ 179 | HNDTYPE_STRONG = 2, 180 | HNDTYPE_DEFAULT = 2, 181 | 182 | /* 183 | * PINNED HANDLES 184 | * 185 | * Pinned handles are strong handles which have the added property that they 186 | * prevent an object from moving during a garbage collection cycle. This is 187 | * useful when passing a pointer to object innards out of the runtime while GC 188 | * may be enabled. 189 | * 190 | * NOTE: PINNING AN OBJECT IS EXPENSIVE AS IT PREVENTS THE GC FROM ACHIEVING 191 | * OPTIMAL PACKING OF OBJECTS DURING EPHEMERAL COLLECTIONS. THIS TYPE 192 | * OF HANDLE SHOULD BE USED SPARINGLY! 193 | */ 194 | HNDTYPE_PINNED = 3, 195 | 196 | /* 197 | * VARIABLE HANDLES 198 | * 199 | * Variable handles are handles whose type can be changed dynamically. They 200 | * are larger than other types of handles, and are scanned a little more often, 201 | * but are useful when the handle owner needs an efficient way to change the 202 | * strength of a handle on the fly. 203 | * 204 | */ 205 | HNDTYPE_VARIABLE = 4, 206 | 207 | /* 208 | * REFCOUNTED HANDLES 209 | * 210 | * Refcounted handles are handles that behave as strong handles while the 211 | * refcount on them is greater than 0 and behave as weak handles otherwise. 212 | * 213 | * N.B. These are currently NOT general purpose. 214 | * The implementation is tied to COM Interop. 215 | * 216 | */ 217 | HNDTYPE_REFCOUNTED = 5, 218 | 219 | /* 220 | * DEPENDENT HANDLES 221 | * 222 | * Dependent handles are two handles that need to have the same lifetime. One handle refers to a secondary object 223 | * that needs to have the same lifetime as the primary object. The secondary object should not cause the primary 224 | * object to be referenced, but as long as the primary object is alive, so must be the secondary 225 | * 226 | * They are currently used for EnC for adding new field members to existing instantiations under EnC modes where 227 | * the primary object is the original instantiation and the secondary represents the added field. 228 | * 229 | * They are also used to implement the managed ConditionalWeakTable class. If you want to use 230 | * these from managed code, they are exposed to BCL through the managed DependentHandle class. 231 | * 232 | * 233 | */ 234 | HNDTYPE_DEPENDENT = 6, 235 | 236 | /* 237 | * PINNED HANDLES for asynchronous operation 238 | * 239 | * Pinned handles are strong handles which have the added property that they 240 | * prevent an object from moving during a garbage collection cycle. This is 241 | * useful when passing a pointer to object innards out of the runtime while GC 242 | * may be enabled. 243 | * 244 | * NOTE: PINNING AN OBJECT IS EXPENSIVE AS IT PREVENTS THE GC FROM ACHIEVING 245 | * OPTIMAL PACKING OF OBJECTS DURING EPHEMERAL COLLECTIONS. THIS TYPE 246 | * OF HANDLE SHOULD BE USED SPARINGLY! 247 | */ 248 | HNDTYPE_ASYNCPINNED = 7, 249 | 250 | /* 251 | * SIZEDREF HANDLES 252 | * 253 | * SizedRef handles are strong handles. Each handle has a piece of user data associated 254 | * with it that stores the size of the object this handle refers to. These handles 255 | * are scanned as strong roots during each GC but only during full GCs would the size 256 | * be calculated. 257 | * 258 | */ 259 | HNDTYPE_SIZEDREF = 8, 260 | 261 | /* 262 | * NATIVE WEAK HANDLES 263 | * 264 | * Native weak reference handles hold two different types of weak handles to any 265 | * RCW with an underlying COM object that implements IWeakReferenceSource. The 266 | * object reference itself is a short weak handle to the RCW. In addition an 267 | * IWeakReference* to the underlying COM object is stored, allowing the handle 268 | * to create a new RCW if the existing RCW is collected. This ensures that any 269 | * code holding onto a native weak reference can always access an RCW to the 270 | * underlying COM object as long as it has not been released by all of its strong 271 | * references. 272 | */ 273 | HNDTYPE_WEAK_NATIVE_COM = 9 274 | } 275 | 276 | // Arguments to GCToEEInterface::StompWriteBarrier 277 | [StructLayout(LayoutKind.Sequential)] 278 | public unsafe struct WriteBarrierParameters 279 | { 280 | // The operation that StompWriteBarrier will perform. 281 | public WriteBarrierOp operation; 282 | 283 | // Whether or not the runtime is currently suspended. If it is not, 284 | // the EE will need to suspend it before bashing the write barrier. 285 | // Used for all operations. 286 | public bool is_runtime_suspended; 287 | 288 | // Whether or not the GC has moved the ephemeral generation to no longer 289 | // be at the top of the heap. When the ephemeral generation is at the top 290 | // of the heap, and the write barrier observes that a pointer is greater than 291 | // g_ephemeral_low, it does not need to check that the pointer is less than 292 | // g_ephemeral_high because there is nothing in the GC heap above the ephemeral 293 | // generation. When this is not the case, however, the GC must inform the EE 294 | // so that the EE can switch to a write barrier that checks that a pointer 295 | // is both greater than g_ephemeral_low and less than g_ephemeral_high. 296 | // Used for WriteBarrierOp::StompResize. 297 | public bool requires_upper_bounds_check; 298 | 299 | // The new card table location. May or may not be the same as the previous 300 | // card table. Used for WriteBarrierOp::Initialize and WriteBarrierOp::StompResize. 301 | public uint* card_table; 302 | 303 | // The new card bundle table location. May or may not be the same as the previous 304 | // card bundle table. Used for WriteBarrierOp::Initialize and WriteBarrierOp::StompResize. 305 | public uint* card_bundle_table; 306 | 307 | // The heap's new low boundary. May or may not be the same as the previous 308 | // value. Used for WriteBarrierOp::Initialize and WriteBarrierOp::StompResize. 309 | public byte* lowest_address; 310 | 311 | // The heap's new high boundary. May or may not be the same as the previous 312 | // value. Used for WriteBarrierOp::Initialize and WriteBarrierOp::StompResize. 313 | public byte* highest_address; 314 | 315 | // The new start of the ephemeral generation. 316 | // Used for WriteBarrierOp::StompEphemeral. 317 | public byte* ephemeral_low; 318 | 319 | // The new end of the ephemeral generation. 320 | // Used for WriteBarrierOp::StompEphemeral. 321 | public byte* ephemeral_high; 322 | 323 | // The new write watch table, if we are using our own write watch 324 | // implementation. Used for WriteBarrierOp::SwitchToWriteWatch only. 325 | public byte* write_watch_table; 326 | 327 | // mapping table from region index to generation 328 | public byte* region_to_generation_table; 329 | 330 | // shift count - how many bits to shift right to obtain region index from address 331 | public byte region_shr; 332 | 333 | // whether to use the more precise but slower write barrier 334 | public bool region_use_bitwise_write_barrier; 335 | } 336 | 337 | // Different operations that can be done by GCToEEInterface::StompWriteBarrier 338 | public enum WriteBarrierOp 339 | { 340 | StompResize, 341 | StompEphemeral, 342 | Initialize, 343 | SwitchToWriteWatch, 344 | SwitchToNonWriteWatch 345 | } 346 | -------------------------------------------------------------------------------- /ManagedDotnetGCLoader/ManagedDotnetGCLoader.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {b22b0b4b-c3d2-4141-85c1-262ac52218b2} 25 | ManagedDotnetGCLoader 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | true 77 | WIN32;_DEBUG;MANAGEDDOTNETGCLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 78 | true 79 | Use 80 | pch.h 81 | 82 | 83 | Windows 84 | true 85 | false 86 | 87 | 88 | 89 | 90 | Level3 91 | true 92 | true 93 | true 94 | WIN32;NDEBUG;MANAGEDDOTNETGCLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 95 | true 96 | Use 97 | pch.h 98 | 99 | 100 | Windows 101 | true 102 | true 103 | true 104 | false 105 | 106 | 107 | 108 | 109 | Level3 110 | true 111 | _DEBUG;MANAGEDDOTNETGCLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 112 | true 113 | Use 114 | pch.h 115 | 116 | 117 | Windows 118 | true 119 | false 120 | 121 | 122 | 123 | 124 | Level3 125 | true 126 | true 127 | true 128 | NDEBUG;MANAGEDDOTNETGCLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 129 | true 130 | Use 131 | pch.h 132 | 133 | 134 | Windows 135 | true 136 | true 137 | true 138 | false 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | Create 149 | Create 150 | Create 151 | Create 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /ManagedDotnetGCLoader/ManagedDotnetGCLoader.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | -------------------------------------------------------------------------------- /ManagedDotnetGCLoader/dllmain.cpp: -------------------------------------------------------------------------------- 1 | // dllmain.cpp : Defines the entry point for the DLL application. 2 | #include "pch.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef HRESULT(__stdcall* f_GC_Initialize)(void*, void*, void*, void*); 9 | typedef HRESULT(__stdcall* f_GC_VersionInfo)(void*); 10 | 11 | static f_GC_Initialize s_gcInitialize; 12 | static f_GC_VersionInfo s_gcVersionInfo; 13 | 14 | BOOL APIENTRY DllMain(HMODULE hModule, 15 | DWORD ul_reason_for_call, 16 | LPVOID lpReserved 17 | ) 18 | { 19 | if (ul_reason_for_call == 0) 20 | { 21 | std::cout << "[Loader] Detaching ManagedDotnetGC loader" << std::endl; 22 | return true; 23 | } 24 | else if (ul_reason_for_call != 1) 25 | { 26 | return true; 27 | } 28 | 29 | std::cout << "[Loader] Loading module" << std::endl; 30 | auto module = LoadLibraryA("ManagedDotnetGC.dll"); 31 | 32 | if (module == 0) 33 | { 34 | std::cout << "Could not find ManagedDotnetGC.dll" << std::endl; 35 | return FALSE; 36 | } 37 | 38 | std::cout << "[Loader] Successfully loaded ManagedDotnetGC.dll" << std::endl; 39 | 40 | s_gcInitialize = (f_GC_Initialize)GetProcAddress(module, "Custom_GC_Initialize"); 41 | s_gcVersionInfo = (f_GC_VersionInfo)GetProcAddress(module, "Custom_GC_VersionInfo"); 42 | 43 | return true; 44 | } 45 | 46 | extern "C" __declspec(dllexport) HRESULT GC_Initialize( 47 | void* clrToGC, 48 | void** gcHeap, 49 | void** gcHandleManager, 50 | void* gcDacVars 51 | ) 52 | { 53 | std::cout << "[Loader] Forwarding call to GC_Initialize" << std::endl; 54 | return s_gcInitialize(clrToGC, gcHeap, gcHandleManager, gcDacVars); 55 | } 56 | 57 | extern "C" __declspec(dllexport) void GC_VersionInfo(void* result) 58 | { 59 | std::cout << "[Loader] Fowarding call to GC_VersionInfo" << std::endl; 60 | s_gcVersionInfo(result); 61 | } 62 | 63 | -------------------------------------------------------------------------------- /ManagedDotnetGCLoader/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | // Windows Header Files 5 | #include 6 | -------------------------------------------------------------------------------- /ManagedDotnetGCLoader/pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to the pre-compiled header 2 | 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /ManagedDotnetGCLoader/pch.h: -------------------------------------------------------------------------------- 1 | // pch.h: This is a precompiled header file. 2 | // Files listed below are compiled only once, improving build performance for future builds. 3 | // This also affects IntelliSense performance, including code completion and many code browsing features. 4 | // However, files listed here are ALL re-compiled if any one of them is updated between builds. 5 | // Do not add files here that you will be updating frequently as this negates the performance advantage. 6 | 7 | #ifndef PCH_H 8 | #define PCH_H 9 | 10 | // add headers that you want to pre-compile here 11 | #include "framework.h" 12 | 13 | #endif //PCH_H 14 | -------------------------------------------------------------------------------- /TestApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32519.111 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{911E67C6-3D85-4FCE-B560-20A9C3E3FF48}") = "TestApp", "TestApp\bin\Debug\net6.0\win-x64\TestApp.exe", "{FEE2A187-BA9D-48E4-A7AB-0E07399A7D7B}" 7 | ProjectSection(DebuggerProjectSystem) = preProject 8 | PortSupplier = 00000000-0000-0000-0000-000000000000 9 | Executable = C:\git\ManagedDotnetGC\TestApp\bin\Debug\net6.0\win-x64\TestApp.exe 10 | RemoteMachine = ARSTANEK 11 | StartingDirectory = C:\git\ManagedDotnetGC\TestApp\bin\Debug\net6.0\win-x64 12 | Environment = complus_gcname=ManagedDotnetGCLoader.dll 13 | LaunchingEngine = 00000000-0000-0000-0000-000000000000 14 | UseLegacyDebugEngines = No 15 | LaunchSQLEngine = No 16 | AttachLaunchAction = No 17 | IORedirection = Auto 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Release|x64 = Release|x64 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {FEE2A187-BA9D-48E4-A7AB-0E07399A7D7B}.Release|x64.ActiveCfg = Release|x64 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(ExtensibilityGlobals) = postSolution 31 | SolutionGuid = {31EF928F-57F2-49BD-B51A-F4F1736E98BB} 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /TestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using TestApp; 2 | 3 | Console.WriteLine("Hello, World!"); 4 | 5 | StaticClass.Root = new MyOwnObject(); 6 | 7 | while (true) 8 | { 9 | Console.WriteLine("Press return to allocate"); 10 | 11 | if (Console.ReadLine() == "q") 12 | { 13 | return; 14 | } 15 | 16 | // var b = new byte[1024 * 1024 * 8]; 17 | 18 | for (int i = 0; i < 1024 * 1024; i++) 19 | { 20 | var obj = new MyOwnObject { Str = new string('c', 10), Obj = new() }; 21 | } 22 | 23 | GC.Collect(); 24 | } 25 | 26 | 27 | public class MyOwnObject 28 | { 29 | public string Str; 30 | public Object Obj; 31 | } 32 | -------------------------------------------------------------------------------- /TestApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "TestApp": { 4 | "commandName": "Project", 5 | "nativeDebugging": true, 6 | "environmentVariables": { 7 | "COMPlus_GCName": "ManagedDotnetGC.dll" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /TestApp/StaticClass.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp; 2 | 3 | internal class StaticClass 4 | { 5 | public static object Root; 6 | } 7 | -------------------------------------------------------------------------------- /TestApp/TestApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | x64 9 | win-x64 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /TestApp/launch.cmd: -------------------------------------------------------------------------------- 1 | @set COMPlus_GCPath=ManagedDotnetGC.dll 2 | @set DOTNET_GCName=clrgc.dll 3 | @TestApp.exe -------------------------------------------------------------------------------- /publish.cmd: -------------------------------------------------------------------------------- 1 | dotnet publish .\ManagedDotnetGC /p:SelfContained=true -r win-x64 -c Release 2 | 3 | copy .\ManagedDotnetGC\bin\Release\net9.0\win-x64\publish\* .\TestApp\bin\Debug\net9.0\win-x64\ --------------------------------------------------------------------------------