绕过 .NET 中的 2 GB 收集限制
本文关键字:GB NET 中的 绕过 | 更新日期: 2023-09-27 18:30:30
从这个问题中,我想我可以使用以下模式创建一个 BigList 数据类型来绕过 2 GB 的集合大小限制(顺便说一下,这个限制似乎是默认强加在 x86 应用程序上的,如果你好奇的话):
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace RegistryHawk
{
class Program
{
struct RegistryPath
{
public RegistryView View;
public string Path;
public bool IsKey;
public RegistryValueKind ValueKind;
public string ValueName;
public object Value;
public int HashValue;
}
public class BigList<T>
{
object listLock = new object();
List<List<T>> Items = new List<List<T>>();
int PageSize = 1000000; // Tweak this to be the maximum size you can grow each individual list before reaching the 2 GB size limit of .NET.
public ulong Count = 0;
int listCount = 0;
public BigList()
{
Items.Add(new List<T>());
}
public void Add(T item)
{
lock (listLock)
{
if (Items[listCount].Count == PageSize)
{
Items.Add(new List<T>());
listCount++;
}
Items[listCount].Add(item);
Count++;
}
}
}
static void Main(string[] args)
{
BigList<RegistryPath> snapshotOne = new BigList<RegistryPath>();
WalkTheRegistryAndPopulateTheSnapshot(snapshotOne);
BigList<RegistryPath> snapshotTwo = new BigList<RegistryPath>();
WalkTheRegistryAndPopulateTheSnapshot(snapshotTwo);
}
private static void WalkTheRegistryAndPopulateTheSnapshot(BigList<RegistryPath> snapshot)
{
List<ManualResetEvent> handles = new List<ManualResetEvent>();
foreach (RegistryHive hive in Enum.GetValues(typeof(RegistryHive)))
{
foreach (RegistryView view in Enum.GetValues(typeof(RegistryView)).Cast<RegistryView>().ToList().Where(x => x != RegistryView.Default))
{
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
handles.Add(manualResetEvent);
new Thread(() =>
{
WalkKey(snapshot, view, RegistryKey.OpenBaseKey(hive, view));
manualResetEvent.Set();
}).Start();
}
}
ManualResetEvent.WaitAll(handles.ToArray());
}
private static void WalkKey(BigList<RegistryPath> snapshot, RegistryView view, RegistryKey key)
{
RegistryPath path = new RegistryPath { View = view, Path = key.Name, HashValue = (view.GetHashCode() ^ key.Name.GetHashCode()).GetHashCode() };
snapshot.Add(path);
string[] valueNames = null;
try
{
valueNames = key.GetValueNames();
}
catch { }
if (valueNames != null)
{
foreach (string valueName in valueNames)
{
RegistryValueKind valueKind = RegistryValueKind.Unknown;
try
{
valueKind = key.GetValueKind(valueName);
}
catch { }
object value = key.GetValue(valueName);
RegistryPath pathForValue = new RegistryPath { View = view, Path = key.Name, ValueKind = valueKind, ValueName = valueName, Value = value, HashValue = (view.GetHashCode() ^ key.Name.GetHashCode() ^ valueKind.GetHashCode() ^ valueName.GetHashCode()).GetHashCode() };
snapshot.Add(pathForValue);
}
}
string[] subKeyNames = null;
try
{
subKeyNames = key.GetSubKeyNames();
}
catch { }
if (subKeyNames != null)
{
foreach (string subKeyName in subKeyNames)
{
try
{
WalkKey(snapshot, view, key.OpenSubKey(subKeyName));
}
catch { }
}
}
}
}
}
但是,CLR 仍会触发System.OutOfMemory
异常。它没有被扔到任何地方,但我看到程序执行在大约 2 GB 的 RAM 处完全停止,当我在 Visual Studio 中冻结我的代码时,它显示每当我尝试查看应用程序任何线程中的变量状态时,都会引发内存不足异常。它永远不会发生在第一次调用WalkTheRegistryAndPopulateTheSnapshot(snapshotOne);
时,但是当第二次调用WalkTheRegistryAndPopulateTheSnapshot(snapshotTwo);
继续进行时,它最终会在我集合中大约 2 GB 的总 RAM 使用率下停止程序执行。整个代码都已发布,因此,如果您有一个强大的注册表,您可能会看到它在 x86 控制台应用程序上生成。我在这里没有掌握什么,或者这种模式不是绕过 Stack 上另一个问题似乎达到的 2 GB 集合大小限制的有效方法?
扩展我的评论。如果你正在编写 32 位应用,则在处理大量数据时会有一些严重的内存限制。
要记住的最重要的一点是,32 位应用程序被限制为绝对最大 2^32 字节 (4 GB) 的内存。实际上,它通常是 2 GB,或者如果您拥有如此多的内存并且应用程序可以识别大地址,则可能是 3 GB。
还有 .NET 施加的 2 GB 限制,它将任何单个对象的大小限制为不超过 2 GB。在 32 位程序中很少会遇到此限制,这仅仅是因为,即使在内存超过 2 GB 的计算机上,也不太可能有大小为 2 GB 的连续内存块。
64 位版本的 .NET 中也存在 2 GB 限制,除非你运行的是 .NET 4.5 并使用启用大型对象的 app.config 设置。
至于为什么在 32 位版本中存在类似 BigList
的东西,这是一种绕过需要连续内存块的方法。例如,包含 2.5 亿个项目的List<int>
需要千兆字节:大小为 1 GB 的连续内存块。但是,如果您使用BigList
技巧(就像您在代码中所做的那样),那么您需要 250 个单独的内存块,大小为 4 MB。您拥有 250 个 4 MB 块的可能性比单个 1 GB 块的可能性要大得多。