9

この 1 時間は、C# のアンマネージ メモリに関する奇妙な問題に取り組んでいたところです。

まず、少しコンテキストを説明します。Delphi アプリケーションによって呼び出されるいくつかのネイティブ メソッドを (この素晴らしいプロジェクト テンプレートを介して) エクスポートする C# DLL があります。これらの C# メソッドの 1 つは、レコードにキャストされる構造体を Delphi に戻す必要があります。吐き気を催していることはすでにわかっているので、これ以上詳しく説明することはしません。はい、醜いですが、代替手段は COM です...いいえ。

問題のあるコードの簡略化は次のとおりです。

IntPtr AllocBlock(int bufferSize)
{
    IntPtr ptrToMem = Marshal.AllocHGlobal(bufferSize);

    // zero memory
    for(int i = 0; i < bufferSize; i++)
       Marshal.WriteInt16(ptrToMem, i, 0);

    return ptrToMem;
}

実際には、ネイティブ リソース トラッキングに関連して、他にもいくつかのことが起こっていますが、基本的にはそれだけです。すでにバグを発見した場合は、よくできています。

本質的に、問題は、IntelliSense 支援のタイプミスにより、WriteInt16代わりにを使用WriteByteしていたことであり、最終的な反復でバッファーの末尾に 1 バイトが書き込まれました。間違いやすいと思います。

しかし、これをデバッグするのが非常に面倒だったのは、デバッガー内でサイレントに失敗し、アプリケーションの残りの部分が動作し続けたためです。メモリは割り当てられており、最後のバイト以外はすべてゼロになっているため、正常に機能しました。デバッガーの外部で起動すると、アプリケーションがアクセス違反でクラッシュしました。典型的な Heisenbug の状況 - 分析しようとすると、バグが消えてしまいます。このクラッシュマネージド例外ではなく、実際の CPU レベルのアクセス違反であることに注意してください。

さて、これは2つの理由で私を困惑させます:

  1. デバッガーがアタッチされている場合、例外はスローされませんでした-Visual Studio、CodeGear Delphi 2009、および OllyDbg を試しました。接続すると、プログラムは完全に機能しました。接続されていない場合、プログラムがクラッシュしました。すべての試行でまったく同じ実行可能ファイルを使用しました。私の理解では、デバッグによってアプリケーションの動作が変わるべきではありませんが、明らかに変更されています。

  2. AccessViolationException通常、この操作によってマネージド コード内でが発生すると予想されますが、代わりに でメモリ アクセス違反が発生しましたntdll.dll

さて、公平に言えば、私のケースはおそらく C# の歴史の中で最もあいまいな (そしておそらく見当違いの) コーナーケースの 1 つですが、デバッガーを接続するとクラッシュがどのように防止されたかについてはわかりません。特に驚いたのは、それが OllyDbg の下で機能したことです。OllyDbg は、Visual Studio ほどプロセスに干渉しません。

それで、ここで一体何が起こったのですか?デバッグ中に例外が飲み込まれた (または発生しなかった) のに、デバッガの外では例外が発生しなかったのはなぜですか? Marshal.WriteInt16また、ドキュメントに記載されているように、割り当てられたメモリ ブロックの外部で呼び出そうとしたときにマネージド アクセス違反の例外がスローされなかったのはなぜですか?

4

1 に答える 1

2

ヒープメモリをゼロに設定するだけです。このアドレスをどこから取得したかチェックがないため、これは失敗しません。これにより、後でヒープ管理(ntdll内)で問題が発生します。このページは、オーバーランが後でのみ検出される理由を示しています。

同様の理由で、接続されたデバッガーで失敗することはありません。Windowsがデバッガーが接続されていることを検出すると、別のヒープマネージャーが使用されます。デバッグヒープマネージャーは、オーバーランを検出するためのサフィックスを追加します。これは、ヒープマネージャーを無効にしませんが、ブロックを解放する際の破損を示します。詳細については、上のページを参照してください。

ヒープマネージャーがOSレベルでどのように切り替えられるかはわかりませんが、stackoverflowに関連する回答が見つかりました: Visual C ++:リリースモードでのデバッグありとなしの違い

ヒープマネージャーの切り替えを理解しているので、デスクトップ/コンソールからリリースモードでプロセスを開始し、後でデバッガーを接続すると、標準のヒープマネージャーが使用されるため、デバッガーはヒープの破損で停止するはずです。これは私の仮定のテストとして使用できます。

于 2012-10-03T17:18:19.430 に答える