获取已加载程序集的内存地址

Get Memory Address of Loaded Assemblies

我需要在我的应用程序域中获取 已加载程序集 的内存地址。 当程序集加载到 .Net 应用程序时,它们将完全加载到主应用程序内存中。

如果我们在内存中搜索此字节模式:

byte[] pe_pattern = {
    0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
    0xFF, 0xFF
};

我们找到了它们在内存中的位置和地址。 但是我需要在没有内存扫描的情况下执行此操作 因为性能。

我试图通过 AppDomain.CurrentDomain.GetAssemblies() 获取加载的程序集,并通过垃圾收集器将它们的地址作为对象获取,其他一些方法可以在这里找到:Memory address of an object in C#

但是我得到的地址不是正确的地址,我没有错误。

在 C++ 中有一种方法可以通过 loadlibrary 获取加载的 dll,但在 C# 中我找不到任何东西。

如何在我的 C# 应用程序中获取已加载程序集的内存地址?

我不确定您是在查找 (1) 映射的程序集文件的虚拟地址,还是 (2) 加载程序集后放置 JIT 代码的虚拟地址。

接下来我将考虑主机进程加载几个程序集的简单情况。可以找到代码 here。让我们关注加载 x64_Assembly.dll 时发生的情况。

如果我们要查找的是上面定义的 (1)(映射文件在进程地址 space 中的虚拟地址),那么这意味着下面突出显示的行,如输出所示的VMMap。这是 OS 加载包含程序集的文件的地方。我不知道你如何从你自己的应用程序中以编程方式获得这个。

对于(2),这是程序集的JITed代码所在的虚拟地址,如果你用调试器进入你的代码,你可以实际看到相应的地址:

正如 this thread 指出的那样,JITed 程序集被放置在堆中,您可以再次使用 VMMap 轻松验证。在我的例子中,可以看到调试器中显示的地址位于带有 VMMap 的堆块内:

那么您实际定位的是哪个地址?

稍后更新: 您可以使用 CLR MD 获取非常有趣的数据。看一下下面的简单代码(取自 Ben Watson 的 "Writing High-Performance .NET Code"),它获得 (1) 和 可能 (2)。可以看到VMMap中加载的程序集的图像地址与module.ImageBase的值匹配,所以肯定得到(1)。但是,对于 (2),module.Address 的值与我在原始答案中调试器中看到的 m_assembly 变量不同 - 因此其中一个显示了其他内容。但是,如果您考虑一下,并不是所有的代码都同时进行了 JIT——相反,CLR 将在方法被调用时(如果)进行 JIT 编译。因此我相信这两个变量包含的虚拟地址指向一些代表程序集的通用结构。

既然你提到你确实有权检查内存内容,你可以很快找出 2 个变量中的哪一个对 (2) 感兴趣。

你怎么能在实践中做到这一点?我正在考虑构建 CLR MD 项目,该项目仅输出您之后的信息((1) 和 (2) 在一个简单的文件中)),然后让您的主代码调用此 EXE,以便它分析您的过程和它加载和写入数据的程序集。当 CLR MD 进程终止时,您的实际代码可以检索文件中写入的信息并对它检索到的那些虚拟地址进行操作。在我上面的示例中,PID 只是硬编码的(我使用 Process Explorer 查看分配的 PID),但您可以将它作为参数传递给您的 CLR MD 项目。

您可以使用 Visual Studio 中的 Manage NuGet Packages for Solution 选项来安装 CLR MD,并为您的特定项目配置它,然后只需添加一个 using Microsoft.Diagnostics.Runtime.

2个需要注意的事项:

  • 您正在使用的 CLR MD 代码的 "bitness" 必须与您正在分析的过程相匹配(例如,不要为 x86 构建一个而另一个为 x64;有关程序集和交叉的完整详细信息-位数加载在 the article I've previously referenced)
  • 您必须在 AttachToProcess 方法中使用 AttachFlag.Passive,否则您的原始代码将无限期暂停。在截取上面的屏幕截图并成功获得 module.ImageBasemodule.Address 值后,我也使用此选项进行了测试,加上初始代码继续 运行 就好了。