【问题标题】:Get Memory Address of Loaded Assemblies获取加载程序集的内存地址
【发布时间】:2019-07-24 08:34:11
【问题描述】:

我需要在我的 appdomain 中获取 已加载程序集 的内存地址。 当程序集加载到 .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# 应用程序中获取已加载程序集的内存地址?

【问题讨论】:

  • 你不能,因为 C# 没有公开这个功能。
  • @IanKemp 你确定吗?我看到有些人在 .Net 应用程序中这样做:(

标签: c# memory .net-assembly


【解决方案1】:

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

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

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

对于(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

需要牢记的两件事:

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

【讨论】:

  • 首先,感谢您的出色回答!是的,我需要获得这两个地址。你有什么建议可以实现吗?
  • 这很有趣,但是如果我从字节数组加载程序集呢?还是使用 Fody.Costura 嵌入 dll ?
  • 您确实可以使用将字节数组作为输入的 Assembly.Load 版本。假设您将程序集硬编码为字节数组(或以某种方式将其嵌入您的代码中),那么包含它的变量将最终出现在堆上的某个位置。 CLR MD 可以再次用于遍历堆,并识别它的虚拟地址,有点类似于上面的示例。请注意,您正在搜索的字节模式(发布在您的问题中)实际上是 MS-DOS 标头的开头,存在于每个 .exe 或 .dll 中。当 JIT 编译器真正将 IL 代码转换为本机代码时...
  • ...对于遇到的每种方法,我相信(我这边的假设)这次不会麻烦在任何地方存储 MS-DOS 标头,但只需将相应方法的本机代码放在内存中的某个位置。因此模式搜索的方法不适用于(2),但是您可以使用 CLR MD 通过枚举它们来获取每个方法的虚拟地址,但我认为这不是您所追求的。
  • 假设,我正在使用 fody Costura 从内存中加载程序集。转储程序扫描内存并通过“MZ ....”字节找到它们,然后将它们转储到磁盘上,一些转储程序通过使用 vmmap 之类的方法获取它们并在没有模式扫描的情况下在内存中找到它们,我的目标是从我的 C# 应用程序中找到它们并修补它们的标题以使转储者无法找到和转储它们,现在有什么想法吗?如果您有兴趣,我们可以研究它。
猜你喜欢
  • 2012-12-06
  • 1970-01-01
  • 1970-01-01
  • 2015-02-26
  • 1970-01-01
  • 1970-01-01
  • 2021-11-21
  • 1970-01-01
  • 2013-10-21
相关资源
最近更新 更多