alf*_*les 14 c++ dll exception c++builder c++builder-2010
我有一个大型应用程序,最近开始在调试器中运行时表现出相当奇怪的行为.一,基础知识:
OS: Windows 7 64-bit.
Application: Multithreaded VCL app with many dlls, bpls, and other components.
Compiler/IDE: Embarcadero RAD Studio 2010.
Run Code Online (Sandbox Code Playgroud)
观察到的症状是这样的:当调试器附加到我的应用程序时,某些任务会导致应用程序崩溃.这些细节进一步令人困惑:我的应用程序因Windows消息"YouApplication已停止工作"而停止.它有助于向微软发送一个小型转储.
应该注意:当未附加调试器时,应用程序不会崩溃.此外,调试器在应用程序运行时不会指示任何异常或其他问题.
设置和单步执行断点似乎会影响应用程序崩溃的点,但我怀疑这是调试线程而不是有问题的线程的症状.
这些崩溃也发生在我同事的计算机上,我观察到同样的行为.这使我不怀疑在我的计算机上安装了某些东西失败了.遇到此问题的同事也在运行Windows 7 64位.我没有同事没有遇到这个问题.
我从崩溃中收集了一些已分析的完整转储.我发现失败实际上每次都发生在同一个地方.这是来自转储的异常数据(它总是相同的,当然除了ThreadId):
Exception Information
ThreadId: 0x000014C0
Code: 0x4000001F Unknown (4000001F)
Address: 0x773F2507
Flags: 0x00000000
NumberParameters: 0x00000001
0x00000000
Run Code Online (Sandbox Code Playgroud)
Google透露,代码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
Run Code Online (Sandbox Code Playgroud)
值得注意的是,在0x773F24ED处似乎有一个函数epilog,这反而表明RtlQueryCriticalSectionOwner是一个红色鲱鱼.同样,函数epilog对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
Run Code Online (Sandbox Code Playgroud)
(项目选项说False是动态RTL的默认值,尽管在我创建dll项目时它被设置为True.)
该DLL加载LoadLibrary并使用FreeLibrary释放.装载和卸载模块似乎都很好.但是,在卸载库(使用FreeLibrary)后不久,上述线程崩溃了程序.为了调试,我删除了对库的所有实际调用(包括,对于更多测试,DllMain).没有呼叫或不呼叫的组合,DllMain或没有DllMain,或其他任何东西似乎以任何方式改变崩溃的行为.只需加载和卸载dll,稍后就会调用崩溃.
此外,更改dll以使用动态RTL还会导致调试器线程崩溃停止.这是不可取的,因为编译的dll确实可以在没有CodeGear Runtime可用的情况下使用.此外,DLL大小很重要.dll中包含的C代码不使用任何库.(它不包括标题,甚至标准库标题.没有malloc/free,没有printf,没有nothin'.它只包含完全依赖于它们的输入而不需要动态分配的函数.)这也是不可取的,因为"修复"a错误通过改变东西直到它工作而不理解为什么它的工作真的从来都不是一个好的计划.(它往往导致错误复发和奇怪的编码实践.但实际上,在这一点上,如果我找不到任何其他东西,我可能会承认失败了.)
最后,我的问题可能与其中一个问题有关:
任何想法或建议将不胜感激.
小智 9
我通过使用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;
Run Code Online (Sandbox Code Playgroud)
在线程中加载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
Footnote2
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;
}
}
Run Code Online (Sandbox Code Playgroud)
只是一个想法...
也许您需要关闭崩溃的线程。您所观察到的状态似乎比实际错误晚一些。
首先,你的堆栈跟踪对我来说似乎不完整。该线程堆栈的基本根是什么?这条线索的起源是什么?
并且,在 VS 调试器中,可以在异常时中断(调试->异常...->[添加])。那么所有线程都会在异常发生的那一刻冻结。我不知道 RAD,但以编程方式执行此操作的技巧似乎是WaitForDebugEvent()。
我可能是错的,但我认为错误很可能存在于调试器中,而不是您的代码中。在这种情况下,恕我直言,一个丑陋的解决方法是完全可以原谅的。祝你好运!