【问题标题】:Exception handling in manually loaded DLL手动加载的 DLL 中的异常处理
【发布时间】:2015-10-26 15:00:33
【问题描述】:

目前我们正在研究在 x64 平台上将 DLL 手动加载到较低 4Gbs 的进程虚拟地址空间中。之所以需要它,是因为 DLL 是在任何地方都明确使用 32 位类型编写的,并且无法重写。所以我们要在项目中使用这个特性,我们要确保 DLL 只使用较低的 4Gbs 来保持工作。

网上有一些实现手动加载 DLL 的链接,我们以此为基础:link

此变体有效。目前只检测到一些问题:

  1. 现在无法使用源进行调试,操作系统只是看不到此模块,它只是它的内存区域,没有其他内容,因此没有加载 PDB。
  2. 我们的项目是这样实现的,即 DLL 从一些有效负载所在的外部框架调用函数。然后在框架中引发异常(故意,而不是偶尔),这就是问题发生的地方。此异常仍未处理,但处理程序存在于框架的代码中。

当通过 x86 或 x64 上的 LoadLibrary 加载 DLL 时(我们很幸运,它加载到较低的 4Gbs 区域)一切正常。我们可以看到整个 SEH 链(例如在 WinDbg 中)并且异常处理得很好。

当手动加载 DLL 时,WinDbg 显示如下:

>!exchain
Frame 0x01: MSVCR120D!__ExceptionPtr::_RethrowException+0x1e1 (000007fe`d9cf4281)
ehandler MSVCR120D!__GSHandlerCheck (000007fe`d9e11eb0)
Frame 0x0b: error getting module for 000000000214daa1
Frame 0x0c: error getting module for 0000000000000003
Frame 0x0d: error getting module for 0000000100000000
Frame 0x0e: error getting module for 0000000002ffa420
Frame 0x0f: error getting module for 0000000100000000
Frame 0x10: error getting module for 0000000000000004

我们尝试关闭/SafeSEH 选项,但结果相同。我们这样做是因为一种猜测是操作系统可以拒绝处理不在受保护模块中的异常处理程序。

目前对为什么会发生这种情况的猜测是操作系统需要这么说内部可见的模块(在通过合法系统函数 LoadLibrary 加载 DLL 的过程中创建了一些内核对象)异常链可以通过。

您如何看待这个问题的可能解决方案?

编辑:在下面回答。

【问题讨论】:

  • 我想知道这个问题的缺点的原因。是不是写的太复杂了?是哑巴吗?请随时分享您的想法,而不是简单的点击。
  • 您尝试加载的 .dll 是否指定了图像库?我对在 64 位 Windows 上加载 32 位模块并不熟悉,但我猜你可以使用 rebase.exe SDK 工具为你的模块提供低于 4GB 的图像库,Windows 会尝试在那里加载它你。
  • 您忘记通过RtlAddFunctionTable 添加展开表。

标签: c++ windows exception-handling 64-bit loadlibrary


【解决方案1】:

实际上我们已经找到了具有“合法”操作系统功能的解决方案。

请记住,我们尝试加载的 DLL 实际上是一个 x64 模块,但它的编写方式与 x86 类似。它只能处理整个虚拟地址空间中的 4 GB,因为它具有显式转换为 32 位类型。

所以如果你想在指定的地址加载一个DLL,你需要执行以下操作:

  1. 检查是否有足够的空闲内存作为 DLL 的块以适合那里(使用内部的 VirtualQueryEx 函数循环执行此操作)。
  2. 将得到的图像基址对齐到 0x10000(否则尝试加载此类模块后会出现ERROR_BAD_EXE_FORMAT 错误)。
  3. 用新值覆盖默认图像库(执行链接器/BASE 选项的操作)。
  4. 修补重定位(它们将在LoadLibrary 调用后使用从原始基地址计算的偏移量进行遍历,而不是显式设置一个。所以我们要修补这些偏移量,以便操作系统正确加载图像,计算新的图像基址和重定位因此)。如果未完成,则尝试加载此类模块后会出现ERROR_NOACCESS 错误。
  5. 重置 ASLR 标志,允许在动态图像库中加载 DLL,尽管有任何预设(执行链接器 /DYNAMICBASE:NO 选项所做的操作)。
  6. 致电LoadLibrary

完成此算法中的步骤后,我们可以像往常一样使用已加载的模块,它与任何其他已加载的 DLL 没有区别,可以使用源代码进行调试,并且可以正确处理引发的异常。

【讨论】:

【解决方案2】:

您可以存储一个无符号整数并使用全局 std::map,而不是存储 32 位指针。 这将使您的代码使用映射到 void* 的 32 位“指针”。您可能还需要 void* 到 unsigned int 的反向映射。此代码适用于两个操作系统。您还需要一个包含空闲 ID 的 std::set unsigned int。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-03
    • 2020-04-07
    • 1970-01-01
    • 1970-01-01
    • 2011-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多