避免包装 DLL 中的堆栈溢出

Posted

技术标签:

【中文标题】避免包装 DLL 中的堆栈溢出【英文标题】:Avoiding stack overflows in wrapper DLLs 【发布时间】:2010-05-03 19:41:58 【问题描述】:

我有一个要添加全屏后期处理效果的程序。我没有该程序的源代码(它是专有的,尽管开发人员确实向我发送了一份调试符号的副本,.map 格式)。我有编写和工作的效果代码,没有问题。

我现在的问题是将两者联系起来。

到目前为止,我已经尝试了两种方法:

使用Detours修改原程序的导入表。这很好用并且保证稳定,但是我与之交谈过的用户对此并不满意,它需要安装(除了提取档案之外),并且存在一些问题,如果使用 Detours 修补程序在条款下是有效的最终用户许可协议。因此,该选项已失效。

另一个选项是传统的 DLL 替换。我已经包装了 OpenGL (opengl32.dll),我需要程序来加载我的 DLL 而不是系统副本(只需将其放入具有正确名称的程序文件夹中,这很容易)。

然后我需要我的 DLL 来加载 Cg 框架和运行时(依赖于 OpenGL)和其他一些东西。当 Cg 加载时,它会调用我的一些函数,这些函数调用 Cg 函数,并且我往往会出现堆栈溢出和无限循环。我需要能够在子目录中包含 Cg DLL 并仍然使用它们的功能(不确定是否可以让我的 DLL 导入表指向子目录中的 DLL)或者我需要动态链接它们(我' d 宁愿不这样做,只是为了简化构建过程),强制他们引用系统文件(不是我的自定义替换)。

整个链条是:程序加载DLL A(名为opengl32.dll)。 DLL A 加载 Cg.dll 并动态链接 (GetProcAddress) 到 sysdir/opengl32.dll。我现在需要 Cg.dll 来引用 sysdir/opengl32.dll,not DLL A.

如何做到这一点? 编辑:如何在不使用 GetProcAddress 的情况下轻松地做到这一点?如果没有其他办法,我愿意退回到那个位置,但如果可能的话,我宁愿不这样做。

Edit2: 我刚刚在 MSDN 文档中偶然发现了 SetDllDirectory 函数(在完全不相关的搜索中)。乍一看,这看起来像我需要的。是这样,还是我判断错误? (现在去测试一下)

Edit3:我通过做一些不同的事情解决了这个问题。我没有删除 OpenGL32.dll,而是将我的 DLL 重命名为 DInput.dll。它不仅具有必须导出一个函数而不是超过 120 个函数的优点(对于程序、Cg 和 GLEW),我不必担心函数会重新运行(我可以像往常一样链接到 OpenGL) .为了接听我需要拦截的电话,我使用了 Detours。总而言之,它工作得更好。不过,这个问题仍然是一个有趣的问题(希望对将来尝试做疯狂事情的​​其他人有用)。两个答案都很好,所以我还不确定该选哪个...

【问题讨论】:

对于将来遇到此问题的任何人,最好的选择(我后来使用了一段时间)是不要使用 Detours。它有问题,尤其是在 WoW64 中,并且缺少递归预防等基本功能。 EasyHook 在功能、稳定性/性能和一般处理方面都非常出色。 【参考方案1】:

SetDllDirectory 可能不会工作。 Cg.dll 可能只是链接到 OpenGL.dll。当操作系统加载 Cg.dll 时,它看到已经有一个使用该名称(您的)加载的模块,因此它将 Cg 与该名称链接,而不是去寻找其他副本。也就是说,SetDllDirectory 修改的搜索顺序甚至不会发挥作用,因为操作系统不进行任何搜索。

我怀疑您最好的选择确实是检测对您的库的重入调用。当您检测到一个,而不是进行自定义处理时,将调用直接转发到真正的 OpenGL 库,由于调用 LoadLibrary,然后为每个库的函数调用 GetProcAddress,您可以引用该库。

【讨论】:

您对检测这些问题的最简单方法有什么建议吗?我以前没有遇到过这个问题,所以我不熟悉处理它的最佳方法。在每个函数的开头添加一个 sn-p 以在特定条件下调用真正的函数不是问题,但是最好检查什么? 其实我有一个想法。使用简单的全局布尔值是否有效且高效?在我的函数开始时,检查if (cameBack) call realFunc; else cameBack = true; ,然后在执行离开时将其设置为 false。这听起来像是工作。据我所知,我的代码不需要是线程安全的(根据我的调试,该程序有一个渲染线程和一个完全引用 OpenGL 的线程)。 全局变量正是我第一次尝试时使用的。如果您需要多个线程,那么您可以尝试线程本地存储而不是普通的全局存储。如果您拦截的函数之一最终调用了您想要拦截的不同 OpenGL 函数,那么您可以使用特定于函数的全局变量而不仅仅是一个。 【参考方案2】:

您可以使用激活上下文的魔力来尝试解决您的问题。

很大程度上取决于您系统中的第 3 方组件已经拥有清单的天气 - 以及对这些清单的篡改程度可能构成违反许可的情况。

为了解决 dll 版本控制问题,Windows XP 获得了一种称为激活上下文的技术。有时被称为并行组装,甚至是像Application Isolation这样可怕的东西

将大量阅读内容汇总到一个小空间中: 清单是可以描述程序集或描述对程序集的依赖关系的 XML 数据块。程序集是一个清单,加上它的 dll。

它存在的原因是程序集可以采用简单的 dll。 “comctl32.dll”及其版本号(v6),并创建一个更大更唯一名称的东西,以便可以将多个版本的简单dll安全地安装在同一个地方。程序集打算安装在C:\Windows\WinSxS

当清单文件描述程序集中的 dll 时,它称为程序集清单。并且通常与 dll 有不同的名称。

当清单文件描述 dll 或 exe 使用的程序集时,它称为应用程序清单,并且通常作为 RT_MANIFEST 资源嵌入 - 在 res id 为 1 的 EXE 中,在 res id 为 2 的 Dll 中 - 或在磁盘上作为名称为“appname.exe.manifest”/“dllname.dll.2.manifest”的文件。 应用程序清单定义了一个叫做激活上下文的东西——它基本上是一个命名空间,windows 将在其中搜索东西。 每个清单都会创建一个激活上下文。每个激活上下文都有一个简单的 dll 名称到程序集的映射。

因此,如果您使用 您的 opengl32.dll 文件创建程序集,并为 app.exe 引用(本地 opengl32.dll)文件创建激活上下文,那么所有其余的 dll 可以(并且将)继续使用系统 opengl32.dll 文件,尽管名称非常相似 coff

问题是,应用程序清单的 res-id - 1 - 意味着它用于创建进程默认激活上下文 - 所以所有没有自己显式清单(Cg?)的 dll 都将搜索处理默认空间并找到那个opengl32.dll

因此,您必须为尚未嵌入的 每个 dll 创建清单,确保简单地不引用您的 opengl32.dll 程序集,这应该允许随后恢复到默认搜索顺序并在正常的 system32 位置找到它。

这意味着您的 opengl32.dll 不能在 exe 的文件夹中,因为该文件夹是在 system32 之前搜索 dll 的(您依赖于挂钩的事实)。

系统在搜索程序集时所采用的相当简单的搜索顺序拯救了我们。首先它在 WinSxS 中搜索。你的 Opengl32.dll 不会在那里,安装有一个难题。然后它在 exe 的文件夹中搜索带有程序集名称的 子文件夹,然后它在 exe 的文件夹中直接搜索程序集清单。

这意味着您可以创建一个程序集 - 称为:“OpenGLHook” 您的文件夹结构如下所示:

\appfolder\
  app.exe
  app.exe.manifest                  - contains a dependentAssembly node to OpenGLHook
  OpenGLHook\OpenGLHook.manifest    - contains a file name=opengl32.dll
  OpenGLHook\opengl32.dll           - your hook dll
  yourimpl.dll                      - your implementation dll that linkgs to cg.dll
  cg.dll                            - cg libraries
  cg.dll.2.manifest                 - a stub manifest you put together to ensure cg
                                      doesnt use the app default activation ctx.

嗯,祝你好运吗?

【讨论】:

以上是关于避免包装 DLL 中的堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中处理或避免堆栈溢出

当 Fortran 生成大型内部临时数组时,如何避免堆栈溢出?

使用 setTimeout 避免堆栈溢出

通过map文件了解堆栈分配(STM32MDK5)--避免堆栈溢出

如何增加python中的堆栈大小

用例子解释编程中的栈溢出和堆溢出? [复制]