绕过 .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 集合大小限制的有效方法?

绕过 .NET 中的 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 块的可能性要大得多。