在我的应用程序中,我有相当多的空指针(这是由于历史原因,应用程序最初是用纯 C 编写的)。在我的一个模块中,我知道 void 指针指向可以从已知基类继承的类的实例,但我不能 100% 确定这一点。因此,对 void 指针进行动态强制转换可能会出现问题。可能,void 指针甚至指向一个普通结构(因此结构中没有 vptr)。

我想调查 void 指针指向的内存的前 4 个字节,看看这是否是有效 vtable 的地址。我知道这是平台,甚至可能是特定于编译器版本的,但它可以帮助我推动应用程序向前发展,并在有限的时间内(假设 3 年)消除所有空指针。

有没有办法获取应用程序中所有 vtable 的列表,或者检查指针是否指向有效的 vtable,以及指向 vtable 的实例是否继承自已知的基类?

有帮助吗?

解决方案

我想调查Void-Pointer指向内存的前4个字节,以查看这是否是有效VTable的地址。

你可以这样做,但你不能保证它会起作用。你甚至不知道 void* 是否会指向 vtable。上次我研究这个问题时(5 年多前)我相信某些编译器存储了 vtable 指针 实例*指向的地址。

我知道这是平台,甚至是编译器特定的平台,

它也可能是特定于编译器选项的,具体取决于您使用的优化等等。

但是,这可以帮助我推动应用程序的前进,并在有限的时间内摆脱所有无效分子(例如3年)。

这是您能看到的推动应用程序前进的唯一选择吗?你考虑过其他人吗?

有没有办法获取应用程序中所有VTABLE的列表,

不 :(

或一种检查指针是否指向有效VTable的方法,

没有标准方法。您可以做的是在您最喜欢的调试器中打开一些类指针(或将内存转换为字节并将其记录到文件中)并进行比较,看看它是否有意义。即便如此,您也不能保证您的任何数据(或应用程序中的其他指针)看起来不会足够相似(当转换为字节时)以混淆您喜欢的任何代码。

以及该实例是否指向已知基类的VTable继承?

又不行了。

这里有一些问题(您可能已经考虑过它们)。这些问题的答案可能会给您更多选择,或者可能会给我们提供其他想法来提出:

  • 代码库有多大?引入全局更改是否可行,或者是否可以为此分散功能?

  • 你是否统一对待所有指针(即:您的源代码中是否存在可以插入并添加您自己的元数据的共同点?)

  • 你可以改变你的源代码什么?(如果您有权访问内存分配子例程或者可以插入您自己的内存分配子例程,例如您可以插入您自己的元数据)。

  • 如果在代码的不同部分将不同的数据类型转换为 void*,那么稍后您如何决定这些指针中的内容?您可以使用区分 void* 的代码来确定它们是否是类吗?

  • 您的代码库是否允许重构方法?(通过插入部分代码的替代实现来进行小迭代重构,然后删除初始实现并测试所有内容)

编辑 (建议的解决方案):

执行以下步骤:

  • 定义元数据(基)类

  • 将内存分配例程替换为仅引用标准/旧例程的自定义例程(并确保您的代码仍然适用于自定义例程)。

  • 在每次分配时,分配 the requested size + sizeof(Metadata*) (并确保您的代码仍然有效)。

  • 更换 第一的 sizeof(Metadata*) 分配的字节具有可以轻松测试的标准字节序列(我偏爱 0xDEADBEEF :D)。然后,返回 [allocated address] + sizeof(Metadata*) 到应用程序。在释放时,获取收到的指针,将其递减 `sizeof(Metadata*),然后调用系统/上一个例程来执行释放。现在, 您在代码中分配了一个额外的缓冲区,专门用于每个分配的元数据。

  • 如果您对元数据感兴趣,请创建/获取元数据类指针,然后将其设置在 0xDEADBEEF 区域中。当您需要检查元数据时, reinterpret_cast<Metadata*>([your void* here]), ,递减它,然后检查指针值是否为 0xDEADBEEF(无元数据)或其他值。

请注意,此代码应该仅用于重构 - 对于生产代码来说,它很慢,容易出错,并且通常还有其他您不希望生产代码出现的坏情况。我会让所有这些代码依赖于一些 REFACTORING_SUPPORT_ENABLED 宏永远不会让您的元数据类看到生产版本的光芒(可能除了测试版本)。

其他提示

我会说这是不可能的,而不相关的参考(标题声明)。

如果您想将这些 void 指针替换为正确的接口类型,我认为可以将其自动化:

  1. 浏览你的代码库以获取所有具有虚函数的类的列表,你可以通过编写脚本来快速完成此操作,例如 Perl

  2. 编写一个函数,以 void* 指针作为输入,并迭代这些类,尝试对其进行动态强制转换,如果成功则记录信息,例如接口类型、代码行

  3. 在任何使用 void* 指针的地方调用这个函数,也许你可以用宏包装它,这样你就可以轻松获取文件、行信息

  4. 运行完全自动化(如果有)并分析输出。

在更简单的方法是将过载operator new为您的特定基类。这样,如果你知道你的void *指针是堆对象,那么你也可以用100%的把握确定是否它们是指向你的对象。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top