核心问题:旧的Windows XP游戏在较新的Windows版本上冻结,除非它在Windows XP兼容模式下运行。
即使为此提供代码修复并不是必需的,我仍然希望对此冻结进行修复,因为noobie用户经常在这个问题上运行,并且每次都会询问他们如何修复它。应用程序可执行文件当然没有源代码,因此调试冻结并不是直截了当的。但是,自Windows XP compat mode" fixes"它,我想知道如何。
所以我在网上查了一下,发现有一个名为Shim Engine的东西通过AppHelp.dll提供各种修复。我安装了Windows Compat Toolkit并跟踪了名为FaultTolerantHeap的Shim删除了应用程序冻结。到目前为止一切都很好。
关于这个FTH没有太多信息,但this video与来自微软的Calinoiu先生在他们所做的事情以及FTH中的4个核心策略中提供了足够好的内容:
如视频中所述,这是在来自ntdll的RtlAllocateHeap
和RtlFreeHeap
等功能上完成的。所以我虽然试一试。
下一步我将这两个功能与Microsoft Detours联系起来,并且可以毫无问题地获得访问权限。我将来自我的钩子函数的调用直接重定向到ntdll中的原始函数。游戏运行良好,钩子很好。
然后,我尝试了归零记忆:冻结仍然发生。为每个堆分配附加8个字节:冻结仍然发生。禁用内存释放:冻结消失了! AHA!
所以推迟释放微软的FTH似乎是"治愈"为了冻结。所以下一步是实现延迟免费。我们需要的是一个队列,它接受最大字节数,然后下一个空闲将清除并释放队列中的第一个条目。 Microsoft为其队列使用4MB缓冲区。所以我可以做同样的想法。但是我有一个很大的问题:我需要跟踪将自由编写的堆分配的大小,否则无法判断我的队列有多大。另外,我不想排队超大堆,例如位图图像的缓冲区非常大。
对于微软的FTH,他声称他们在每个堆分配的最后添加了他们的魔术字节。像30个字节+ 8个字节填充。非常多。我认为我可以通过添加一些4字节值作为"只是足够好"来简化它。用于从我的RtlAllocateHeap
挂钩中识别堆的模式(因为我在运行时挂钩)和另外4个字节来存储每个分配的大小,以便在分配达到RtlFreeHeap
时可以访问它。目前的代码:
#define FTH_MAGIC_BYTES 0xFEDCBA98ul
#define FTH_ALLOC_PADDING 0
...
static PVOID __stdcall RtlAllocateHeap_hook(PVOID HeapHandle, ULONG Flags, SIZE_T Size)
{
if(Size==0)
{
return RtlAllocateHeap_orig(HeapHandle, Flags, Size);
}
// For our allocations we assume 32bit application:
// treat everything with size of 4 bytes
Size += 8+FTH_ALLOC_PADDING;
PVOID HeapBase = RtlAllocateHeap_orig(HeapHandle, Flags, Size);
if(HeapBase)
{
*(uint32*)((uint32)HeapBase+4) = FTH_MAGIC_BYTES;
*(uint32*)((uint32)HeapBase) = Size;
return (PVOID)((uint32)HeapBase+8);
}
return HeapBase;
};
static BOOL __stdcall RtlFreeHeap_hook(PVOID HeapHandle, ULONG Flags, PVOID HeapBase)
{
if(HeapBase==0)
{
return RtlFreeHeap_orig(HeapHandle, Flags, HeapBase);
}
if(*(uint32*)((uint32)HeapBase-4)==FTH_MAGIC_BYTES)
{
HeapBase = (PVOID)((uint32)HeapBase-8);
}
return RtlFreeHeap_orig(HeapHandle, Flags, HeapBase);
};
它在启动时崩溃了应用程序。有趣的是,当我运行Debug时,它不会崩溃。在发布它确实。我在Visual Studio中完成了大会,看不出问题。我还监视新分配的以查看字节是否写入堆中 - 它们是在我期望的位置。然而它抛弃了应用程序,我不明白为什么。当我在HeapBase
返回时不更改RtlAllocateHeap_hook
的偏移量时,代码将运行正常。
if(HeapBase)
{
//*(uint32*)((uint32)HeapBase+4) = FTH_MAGIC_BYTES;
//*(uint32*)((uint32)HeapBase) = Size;
return HeapBase;
}
一旦我返回偏移量,应用程序就会再次崩溃。我可以在最后存储我的字节,然后返回没有偏移的HeapBase,但是当我们点击RtlFreeHeap_hook
时,找到那个对齐将是疯狂的。我不知道微软是怎么做到的,除非他们知道堆的大小。我看到的唯一解决方案是使上述方法正确,找出如何以Microsoft方式检索堆的大小,或者试着挂钩malloc或new。
有什么想法吗?也许我错过了一些非常简单的事情: - )
感谢。
Callstack示例:
NTDLL.DLL!76f030bd()
[下面的框架可能不正确和/或缺失,没有为ntdll.dll加载符号] MSVCRT.DLL!7498f480()
MSVCRT.DLL!7498f4e6()
MSVCRT.DLL!7498f52c()
COMCTL32.DLL!73656c62()
game.dat!00413b90()
game.dat!00413fc8()
MSVCRT.DLL!7499edc8()
MSVCRT.DLL!7498a53a()
MSVCRT.DLL!7498a5d6()
MSVCRT.DLL!7498a6a9()
MSVCRT.DLL!7498d255()
MSVCRT.DLL!7498d272()
MSVCRT.DLL!7498a5d6()
MSVCRT.DLL!7498a5d6()
MSVCRT.DLL!7498a53a()
MSVCRT.DLL!7498a5d6()
MSVCRT.DLL!7498a5d6()
MSVCRT.DLL!7498a5d6()
MSVCRT.DLL!7498a5d6()
MSVCRT.DLL!7498a5d6()
MSVCRT.DLL!7498dfd5()
game.dat!0041d4ed()
game.dat!006f0048()
game.dat!004af57d()
game.dat!00413b90()
game.dat!00413fc8()
game.dat!00413b90()
game.dat!00413fc8()
game.dat!004140d8()
game.dat!008fa9ad()
game.dat!0041f018()EAX = 0262CE78 EBX = 00000000 ECX = 7C7EC8D9 EDX = 00000088 ESI = 00B70000 EDI = 00000000 EIP = 76F030BD ESP = 001886F8 EBP = 00188704 EFL = 00210202
7C7EC8E9 = ????
答案 0 :(得分:1)
经过更多测试和阅读后,我在MSDN中看到kernel32.dll也导出了Heap函数。 Kernel32有一个函数HeapSize
,它返回我们需要的东西。似乎内核函数只是通过ntdll对应的。
因此我怀疑ntdll应该与HeapSize
对应。我查看了ntdll的导出并找到了RtlSizeHeap
。此功能在MSDN上未记录。
谷歌搜索返回this page from ntinternals.net。因此,您可以使用内核函数HeapSize
或ntdll函数RtlSizeHeap
,使其指针位于GetProcAddress
。