14

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

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 发送 minidump 的帮助。

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

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

这些崩溃也发生在我同事的计算机上,与我观察到的行为相同。这使我不怀疑在我的计算机上安装某些东西失败了。遇到此问题的同事也在运行 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 处似乎有一个函数 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,什么都没有。它只包含完全依赖于其输入且不需要动态分配的函数。)它也是不可取的,因为“修复”a通过改变东西直到它工作而不理解它为什么工作的错误真的不是一个好计划。(这往往会导致错误重现和奇怪的编码实践。但实际上,在这一点上,如果我找不到其他任何东西,我可能会认输。)

最后,我的问题可能与以下问题之一有关:

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

4

4 回答 4

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

脚注
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;
   }
}
于 2012-09-17T11:10:00.013 回答
0

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

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

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

于 2013-01-25T12:12:26.993 回答
0

只是一个想法...

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

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

而且,在 VS 调试器中,有可能中断异常,(调试->异常...->[添加])。然后所有线程将在异常发生时冻结。我不知道 RAD,但以编程方式执行此操作的技巧似乎是WaitForDebugEvent()

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

于 2011-08-04T10:14:54.190 回答
0

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

但...

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

2)您所显示的堆栈很可能是由于您的代码实际命中“CALL BAD_ADRESS”而被调用的,这可能是由于您自己的代码中的错误而发生的。换句话说,在您加载的 DLL 中,可能有一个函数正在执行某些操作,该函数正在用垃圾覆盖您程序中的可执行代码,然后当 now junk 部分运行时,它会崩溃。

另一种方法是,如果 C++ dll 中的某些内容正在修改它运行的下面的堆栈,那么您的代码稍后会遇到这个问题。

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

于 2012-09-12T19:52:42.387 回答