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

时间:2011-06-20 16:33:52

标签: c++ exception dll 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.

观察到的症状是:当调试器附加到我的应用程序时,某些任务会导致应用程序崩溃。这些细节进一步令人困惑:我的应用程序因Windows消息“YouApplication已停止工作”而停止。它有助于向微软发送一个小型转发器。

应该注意:未附加调试器时应用程序不会崩溃。此外,调试器在应用程序运行时不会指示任何异常或其他问题。

设置和单步执行断点似乎会影响应用程序崩溃的时间点,但我怀疑这是调试线程而不是有问题的线程的症状。

这些崩溃也发生在同事的计算机上,我观察到同样的行为。这使我不怀疑在我的计算机上安装了某些东西失败了。遇到此问题的同事也在运行Windows 7 64位。我没有同事没有遇到这个问题。

我从崩溃中收集了一些已分析的完整转储。我发现失败实际上每次都发生在同一个地方。这是来自转储的异常数据(它总是相同的,当然除了ThreadId):

Exception Information

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

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

值得注意的是,在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以使用Dynamic RTL也会导致调试器线程崩溃停止。这是不可取的,因为编译后的dll确实可以在没有CodeGear Runtime可用的情况下使用。此外,DLL大小很重要。 dll中包含的C代码不使用任何库。 (它不包括标题,甚至标准库标题。没有malloc / free,没有printf,没有nothin'。它只包含完全依赖于它们的输入而不需要动态分配的函数。)这也是不可取的,因为“修复”a错误通过改变东西直到它工作而不理解为什么它的工作真的从来都不是一个好的计划。 (它往往导致错误复发和奇怪的编码实践。但实际上,在这一点上,如果我找不到任何其他东西,我可能会承认失败了。)

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

任何想法或建议都将受到赞赏。

4 个答案:

答案 0 :(得分: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;

在线程中加载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;
   }
}

答案 1 :(得分:0)

只是一个想法......

也许您需要关闭崩溃的线程。您正在观察的状态似乎是在实际错误之后。

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

并且,在VS调试器中,有可能中断异常,(Debug-&gt; Exceptions ...-&gt; [Add])。然后所有线程将在异常发生时冻结。我不喜欢RAD,但以编程方式执行此操作的技巧似乎是 WaitForDebugEvent()

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

答案 2 :(得分:0)

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

但是...

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

2)由于您的代码实际上遇到了“CALL BAD_ADRESS”,而您可能会因为您自己的代码中的错误而发生,因此您显示的堆栈很可能被调用。换句话说,在你加载的DLL中,可能有一个函数正在做一些用垃圾覆盖程序中的可执行代码的东西,然后当现在的垃圾部分运行时,它会崩溃。

另一种方法是,如果C ++ dll中的某些东西正在修改它运行的下面的堆栈,那么你的代码稍后就会出现这种情况。

3)查看DLL的CPU标志设置。 Borland库有时在进入时使用冲突的CPU标志,您可能需要在调用DLL之前保存和恢复。例如,如果您使用Delphi中的C ++调用VST插件并且没有正确设置标记,则可以从VST插件中获得后续除零错误,该插件是在关闭该异常的情况下编译的。

答案 3 :(得分:0)

我们今天遇到了同样的问题。在我们的例子中,如果在调用TOpenDialog-&gt; Execute()之后有一个断点就会发生崩溃(我认为这是使用来自shell32.dll的对话框)(Windows 7 x64,C ++ Builder XE2)

卸载iCloud(v2.1.0.39)后,问题已解决。

不幸的是,我们仍在调查我们的客户在Windows Vista下使用我们的发布产品的类似问题。使用TOpenDialog选择文件后,应用程序崩溃了gdiplus.dll与Access违规,删除iCloud似乎也解决了这个问题。