我有一个大型应用程序,它最近在调试器中运行时开始表现出相当奇怪的行为。一、基础知识:
OS: Windows 7 64-bit.
Application: Multithreaded VCL app with many dlls, bpls, and other components.
Compiler/IDE: Embarcadero RAD Studio 2010.
观察到的症状是:当调试器附加到我的应用程序时,某些任务会导致应用程序崩溃。这些细节更令人困惑:我的应用程序停止并显示一条 Windows 消息,说“您的应用程序已停止工作”。它有助于向 Microsoft 发送小型转储。
需要注意的是:未连接调试器时,应用程序不会崩溃。此外,调试器不会在应用程序运行时指示任何异常或其他问题。
设置和单步执行断点似乎会影响应用程序崩溃的点,但我怀疑这是调试线程而不是有问题的线程的症状。
这些崩溃也发生在我同事的计算机上,与我观察到的行为相同。这使我不会特别怀疑我的计算机上安装了某些失败的东西。我遇到这个问题的同事也在运行 Windows 7 64 位。我没有同事没有遇到过这个问题。
我从崩溃中收集了一些完整的转储分析。我发现故障实际上每次都发生在同一个地方。这是来自转储的异常数据(它总是相同的,当然除了 ThreadId):
Exception Information
ThreadId: 0x000014C0
Code: 0x4000001F Unknown (4000001F)
Address: 0x773F2507
Flags: 0x00000000
NumberParameters: 0x00000001
0x00000000
谷歌透露代码 0x4000001F 实际上是 STATUS_WX86_BREAKPOINT。 Microsoft 无助地将其描述为“Win32 x86 仿真子系统使用的异常状态代码”。
以下是堆栈详细信息(似乎没有变化):
0x773F2507: ntdll.dll+0x000A2507: RtlQueryCriticalSectionOwner + 0x000000E8
0x773F3DAB: ntdll.dll+0x000A3DAB: RtlQueryProcessLockInformation + 0x0000020D
0x773D2ED9: ntdll.dll+0x00082ED9: RtlUlonglongByteSwap + 0x00005C69
0x773F3553: ntdll.dll+0x000A3553: RtlpQueryProcessDebugInformationRemote + 0x00000044
0x74F73677: kernel32.dll+0x00013677: BaseThreadInitThunk + 0x00000012
0x77389F02: ntdll.dll+0x00039F02: RtlInitializeExceptionChain + 0x00000063
0x77389ED5: ntdll.dll+0x00039ED5: RtlInitializeExceptionChain + 0x00000036
值得注意的是,在 0x773F24ED 处似乎有一个函数结语,这表明 RtlQueryCriticalSectionOwner 是一个红鲱鱼。同样,一个函数结语对 RtlQueryProcessLockInformation 产生了怀疑。 0x5C69 偏移量对 RtlUlonglongByteSwap 产生了怀疑。不过,其他符号看起来是合法的。
具体来说, RtlpQueryProcessDebugInformationRemote 看起来是合法的。网上有些人( http://www.cygwin.com/ml/cygwin-talk/2006-q2/msg00050.html )似乎认为它是由调试器创建的,用于收集调试信息。这个理论对我来说似乎很合理,因为它似乎只有在连接调试器时才会出现。
与往常一样,当某些东西破裂时,某些东西会发生变化,从而破坏它。在这种情况下,某些东西正在动态加载一个新的 dll。我可以通过不动态加载特定的 dll 来导致崩溃停止发生。我不相信 dll 加载是相关的,但这里有详细信息,以防万一:
dll源是C。以下是未设置为默认值的编译选项:
Language Compliance: ANSI
Merge duplicate strings: True
Read-only strings: True
PCH usage: Do not use
Dynamic RTL: False
(项目选项说 False 是动态 RTL 的默认值,尽管在我创建 dll 项目时它被设置为 True。)
该 dll 使用 LoadLibrary 加载并使用 FreeLibrary 释放。加载和卸载模块似乎一切正常。然而,在库卸载后不久(使用 FreeLibrary),上述线程使程序崩溃。为了调试,我删除了对库的所有实际调用(包括 DllMain,以便进行更多测试)。调用或不调用的组合、DllMain 或 DllMain 或其他任何东西似乎都不会以任何方式改变崩溃的行为。简单地加载和卸载 dll 会在稍后调用崩溃。
此外,更改 dll 以使用动态 RTL 还会导致调试器线程崩溃停止。这是不可取的,因为编译后的 dll 确实应该在没有 CodeGear Runtime 可用的情况下可用。此外,dll 大小很重要。 dll 中包含的 C 代码不使用任何库。 (它不包括头文件,甚至标准库头文件。没有 malloc/free,没有 printf,没有任何东西。它只包含完全依赖于它们的输入并且不需要动态分配的函数。)这也是不可取的,因为“修复”一个通过改变东西直到它起作用而不理解它为什么起作用来解决问题真的从来都不是一个好的计划。 (这往往会导致错误重现和奇怪的编码实践。但实际上,在这一点上,如果我找不到其他任何东西,我可能会在这方面认输。)
最后,我的问题可能与以下问题之一有关:
任何想法或建议将不胜感激。
最佳答案
我通过使用 PatchINT3 解决方法的修改版本解决了上述问题,该解决方法于 2007 年发布,用于 BDS 2006:
procedure PatchINT3;
const
INT3: Byte = $CC;
NOP: Byte = $90;
var
NTDLL: THandle;
BytesWritten: DWORD;
Address: PByte;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then
Exit;
NTDLL := GetModuleHandle('NTDLL.DLL');
if NTDLL = 0 then
Exit;
Address := GetProcAddress(NTDLL, 'RtlQueryCriticalSectionOwner');
if Address = nil then
Exit;
Inc(Address, $E8);
try
if Address^ <> INT3 then
Exit;
if WriteProcessMemory(GetCurrentProcess, Address, @NOP, 1, BytesWritten)
and (BytesWritten = 1) then
FlushInstructionCache(GetCurrentProcess, Address, 1);
except
//Do not panic if you see an EAccessViolation here, it is perfectly harmless!
on EAccessViolation do
;
else
raise;
end;
end;
在线程中加载 DLL 后调用一次此例程。该补丁修复了 ntdll.dll 版本 6.1.7601.17725 中的用户断点并将其更改为 NOP。
如果预期地址处没有用户断点(INT3 (=$CC) 操作码),则补丁例程不执行任何操作并退出。
希望有所帮助,
安德烈亚斯
脚注
PatchINT3 的原始来源可以在这里找到:
http://coding.derkeiler.com/Archive/Delphi/borland.public.delphi.non-technical/2007-01/msg04431.html
脚注2
C++ 中的相同函数:
void PatchINT3()
{
unsigned char INT3 = 0xCC;
unsigned char NOP = 0x90;
if (Win32Platform != VER_PLATFORM_WIN32_NT)
{
return;
}
HMODULE ntdll = GetModuleHandle(L"NTDLL.DLL");
if (ntdll == NULL)
{
return;
}
unsigned char *address = (unsigned char*)GetProcAddress(ntdll,
"RtlQueryCriticalSectionOwner");
if (address == NULL)
{
return;
}
address += 0xE8;
try
{
if (*address != INT3)
{
return;
}
unsigned long bytes_written = 0;
if (WriteProcessMemory(GetCurrentProcess(), address, &NOP, 1,
&bytes_written) && (bytes_written == 1))
{
FlushInstructionCache(GetCurrentProcess, address, 1);
}
}
catch (EAccessViolation &e)
{
//Do not panic if you see an EAccessViolation
//here, it is perfectly harmless!
}
catch(...)
{
throw;
}
}
关于c++ - Rad Studio 调试器线程中未处理的异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6414514/