Rad Studio调试器线程中出现未处理的异常



我有一个大型应用程序,它最近在调试器中运行时开始表现出相当奇怪的行为。首先,基础:

OS: Windows 7 64-bit.
Application: Multithreaded VCL app with many dlls, bpls, and other components.
Compiler/IDE: Embarcadero RAD Studio 2010.

观察到的症状是:当调试器连接到我的应用程序时,某些任务会导致应用程序崩溃。具体情况更令人困惑:我的应用程序在Windows中停止,并显示一条消息:"YourApplication已停止工作。"它还提供了向Microsoft发送小型转储的帮助。

需要注意的是:当调试器未连接时,应用程序不会崩溃。此外,调试器在应用程序运行时不会指示任何异常或其他问题。

设置和遍历断点似乎会影响应用程序崩溃的点,但我怀疑这是调试有问题线程以外的线程的症状。

这些崩溃也发生在我同事的电脑上,与我观察到的行为相同。这让我不会怀疑我的电脑上安装了什么东西。遇到这个问题的同事也在运行Windows7 64位。我没有同事没有遇到过这个问题。

我收集了一份分析过的崩溃的完整转储文件。我发现故障实际上每次都发生在同一个地方。以下是来自转储的异常数据(它总是一样的,当然除了ThreadId):

Exception Information
ThreadId:         0x000014C0
Code:             0x4000001F Unknown (4000001F)
Address:          0x773F2507
Flags:            0x00000000
NumberParameters: 0x00000001
    0x00000000

谷歌透露,代码0x4000001F实际上是STATUS_WX86BREAKPOINT。微软毫无帮助地将其描述为"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处似乎有一个函数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

(项目选项说False是动态RTL的默认值,尽管在我创建dll项目时它被设置为True。)

dll使用LoadLibrary加载,并使用FreeLibrary释放。模块的装载和卸载似乎都很好。但是,在卸载库(使用FreeLibrary)后不久,前面提到的线程会使程序崩溃。为了调试,我删除了对库的所有实际调用(为了进行更多测试,包括DllMain)。调用或非调用、DllMain或无DllMain的组合,或任何其他似乎都不会以任何方式改变崩溃的行为。简单地加载和卸载dll会在稍后调用崩溃。

此外,将dll更改为使用动态RTL还会导致调试器线程崩溃停止。这是不可取的,因为编译后的dll确实应该在没有CodeGear Runtime可用的情况下可用。此外,dll大小也很重要。dll中包含的C代码不使用任何库。(它不包括头,甚至包括标准库头。没有malloc/free,没有printf,没有nothin。它只包含完全依赖于其输入的函数,不需要动态分配。(这往往会导致错误复发和奇怪的编码实践。但实际上,在这一点上,如果我找不到其他东西,我可能会承认这一点的失败。)

最后,我的问题可能与其中一个问题有关:

  • 调试多线程应用程序后出现系统错误
  • 程序和调试器在没有问题指示的情况下退出

任何想法或建议都将不胜感激。

我通过使用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.760.17725中的一个用户断点,并将其更改为NOP。

如果在期望的地址上没有用户断点(INT3(=$CC)操作码),则修补程序不执行任何操作并退出。

希望能有所帮助,
Andreas

脚注
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;
   }
}

只是一个想法。。。

也许你需要接近崩溃的线程。您观察到的状态似乎有点超出实际错误。

首先,对我来说,你的堆栈跟踪似乎不完整。那个线程的堆栈的基根是什么?那根线的起源是什么?

而且,在VS调试器中,有可能在异常时中断(Debug->exceptions…->[Add])。然后,所有线程都将在异常发生时冻结。我不知道RAD,但用程序实现它的诀窍似乎是WaitForDebugEvent()

我可能错了,但我认为错误很有可能在调试器中,而不是你的代码中。在这种情况下,IMHO完全可以原谅一个简单的解决方法。祝你好运

我无法回答这个问题,因为我看不到代码。。。

但是。。。

1) 在Borland C++中,至少在BDS中的C++中,多线程库中的realloc函数可能存在可证明的问题。您的C++代码使用realloc吗?

2) 您所显示的堆栈很可能是由于您的代码实际命中"CALL BAD_ADRES"而被调用的,而这可能是您自己代码中的错误造成的。换句话说,在您加载的DLL中,可能有一个函数正在用垃圾覆盖程序中的可执行代码,然后当现在的垃圾部分运行时,它就会崩溃。

另一种方法是,如果C++dll中的某个东西正在修改其运行位置下方的堆栈,那么您的代码稍后就会达到这个目的。

3) 检查DLL的CPU标志设置。Borland库有时在输入时使用冲突的CPU标志,在调用DLL之前可能需要保存和恢复。例如,如果你从Delphi中调用一个用C++制作的VST插件,但没有正确设置标志,那么你可能会从关闭该异常的情况下编译的VST插入中得到随后的除以零的错误。

我们今天也遇到了同样的问题。在我们的案例中,如果在调用TOpenDialog->Execute()(我认为它使用的是来自shell32.dll的对话框)(Windows 7 x64,C++Builder XE2)后出现断点,就会发生崩溃

卸载iCloud(v2.1.0.39)后,问题得到了解决。

不幸的是,我们仍在调查类似的问题,我们的客户在Windows Vista下发布产品时遇到了一些问题。在使用TOpenDialog选择文件后,应用程序在gdiplus.dll中崩溃,并违反了访问权限,删除iCloud似乎也解决了问题。

相关内容

最新更新