14

デバッガーで実行すると、最近かなり奇妙な動作を示し始めた大規模なアプリケーションがあります。まず、基本:

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

観測された症状は次のとおりです。デバッガーがアプリケーションに接続されている間、特定のタスクが原因でアプリケーションがクラッシュします。詳細はさらに複雑です。私のアプリケーションは、「YourApplication has stopped working」という Windows メッセージで停止します。また、Microsoft にミニダンプを送信することもできます。

注意してください: デバッガーが接続されていない場合、アプリケーションはクラッシュしません。また、デバッガーは、アプリケーションの実行中に例外やその他の問題を示しません。

ブレークポイントの設定とステップスルーは、アプリケーションがクラッシュするポイントに影響を与えるようですが、問題のあるスレッド以外のスレッドをデバッグすることの兆候であると思われます。

これらのクラッシュは同僚のコンピューターでも発生し、私が観察したのと同じ動作です。これにより、特にコンピューターへのインストールの失敗を疑うことはありません。この問題が発生している同僚も 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 に関数のエピローグがあるように見えることは注目に値します。これはむしろ、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

(プロジェクト オプションでは、Dynamic RTL のデフォルトは False と表示されていますが、dll プロジェクトを作成したときに True に設定されていました。)

dll は LoadLibrary でロードされ、FreeLibrary で解放されます。モジュールのロードとアンロードはすべて問題ないようです。ただし、ライブラリが (FreeLibrary で) アンロードされた直後に、前述のスレッドがプログラムをクラッシュさせます。デバッグのために、ライブラリへの実際の呼び出しをすべて削除しました (さらにテストするために、DllMain を含む)。呼び出しの組み合わせ、呼び出しなし、DllMain の組み合わせ、または DllMain なし、またはその他の組み合わせは、クラッシュの動作を変更するようには見えませんでした。dll をロードおよびアンロードするだけで、後でクラッシュが発生します。

さらに、動的 RTL を使用するように dll を変更すると、デバッガー スレッドのクラッシュも停止します。コンパイルされた dll は、CodeGear ランタイムがなくても実際に使用できるはずなので、これは望ましくありません。また、dll のサイズも重要です。dll に含まれる C コードは、ライブラリを使用しません。(ヘッダーは含まれません。標準ライブラリのヘッダーも含まれます。malloc/free、printf、nothin' は含まれません。入力にのみ依存し、動的割り当てを必要としない関数のみが含まれます。)なぜ機能するのかを理解せずに、機能するまで何かを変更してバグを修正することは、決して良い計画ではありません。(それはバグの再発や奇妙なコーディング慣行につながる傾向があります。しかし、実際には、この時点で他に何も見つからない場合は、この点で敗北を認めるかもしれません.)

最後に、私の問題は次のいずれかの問題に関連している可能性があります。

アイデアや提案をいただければ幸いです。

4

4 に答える 4

9

2007 年に BDS 2006 用に公開された PatchINT3 回避策の修正版を使用して、上記の問題を解決しました。

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 をスレッドにロードした後、このルーチンを 1 回呼び出します。このパッチは、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

コードが見えないのでお答えできません...

しかし...

1) Borland C++ では、少なくとも BDS の C++ では、マルチスレッド ライブラリの realloc 関数に証明可能な問題が発生する可能性があります。あなたの C++ コードは realloc を使用していますか?

2) 表示しているスタックは、コードが実際に「CALL BAD_ADDRESS」にヒットした結果として呼び出される可能性が高く、独自のコードのバグの結果として発生する可能性があります。つまり、ロードした DLL には、プログラム内の実行可能コードをジャンクで上書きしている関数が存在する可能性があり、ジャンク セクションが実行されるとクラッシュします。

もう 1 つの方法は、C++ dll 内の何かが、それが実行される場所の下のスタックを変更し、コードが後でそれをヒットする場合です。

3) DLL の CPU フラグ設定を確認します。Borland ライブラリは、エントリ時に競合する CPU フラグを使用することがあり、DLL を呼び出す前に保存して復元する必要がある場合があります。たとえば、Delphi から C++ で作成された VST プラグインを呼び出し、フラグを適切に設定しないと、その例外をオフにしてコンパイルされた VST プラグインからゼロ除算エラーが発生する可能性があります。

于 2012-09-12T19:52:42.387 に答える
0

ただのアイデア...

おそらく、クラッシュしているスレッドに近づく必要があります。あなたが観察している状態は、実際のエラーの少し後であるようです。

まず、スタック トレースが不完全なようです。そのスレッドのスタックのベース ルートは何ですか? そのスレの発端は?

また、VS デバッガーでは、例外で中断する可能性があります (Debug->Exceptions...->[Add])。次に、例外が発生した瞬間にすべてのスレッドがフリーズします。RADについてはわかりませんが、プログラムでそれを行うためのトリックはWaitForDebugEvent()のようです。

私は間違っているかもしれませんが、バグがあなたのコードではなくデバッガーにある可能性は十分にあると思います。その場合、醜い回避策は私見で完全に許されます。幸運を!

于 2011-08-04T10:14:54.190 に答える