web-dev-qa-db-ja.com

破損した状態の例外を適切に処理する

この質問 に関連して、それらをログに記録してからアプリケーションを終了することのみを目的として、CLRが.NET 4.5.2アプリに破損状態の例外をキャッチするように強制したいと思います。アプリのいくつかの場所にcatch (Exception ex)がある場合、これを行う正しい方法は何ですか?

したがって、_<legacyCorruptedStateExceptionsPolicy>_属性を指定した後、正しく理解すると、すべてのcatch (Exception ex)ハンドラーがAccessViolationExceptionのような例外をキャッチし、問題なく続行します。

ええ、私はcatch (Exception ex)がBad Idea™であることを知っていますが、CLRが少なくとも正しいスタックトレースをイベントログに記録するのであれば、サーバーアプリがすぐに失敗することをお客様に説明したいと思います。午前1時にオフラインで夜を過ごすのは良いことです。しかし、残念ながら、CLRは 無関係な例外 をイベントログに記録してからプロセスを閉じて、実際に何が起こったのかを確認できません。

問題は、これを実現する方法、プロセス全体です。

_if the exception thrown is a Corrupted State Exception:
    - write the message to the log file
    - end the process 
_

(更新)

言い換えれば、これはおそらくシンプルなアプリのほとんどの例外で機能します:

_[HandleProcessCorruptedStateExceptions] 
[SecurityCritical]
static void Main() // main entry point
{
    try 
    {

    }
    catch (Exception ex)
    {
        // this will catch CSEs
    }
}
_

ただし、次の場合は機能しません。

  • 未処理のアプリドメインの例外(非フォアグラウンドスレッドでスローされます)
  • Windowsサービスアプリ(実際のMainエントリポイントがない)

したがって、_<legacyCorruptedStateExceptionsPolicy>_がこれを機能させる唯一の方法であるように思われます。その場合、CSEのログ記録後に失敗する方法がわかりませんか?

24
Lou

_<legacyCorruptedStateExceptionsPolicy>_を使用する代わりに、次のように_[HandleProcessCorruptedStateExceptions]_(および_[SecurityCritical]_)を使用することをお勧めします。

https://msdn.Microsoft.com/en-us/magazine/dd419661.aspx

その後、Mainメソッドは次のようになります。

_[HandleProcessCorruptedStateExceptions, SecurityCritical]
static void Main(string[] args)
{
    try
    {
        ...
    }
    catch (Exception ex)
    {
        // Log the CSE.
    }
}
_

ただし、これはStackOverflowExceptionExecutionEngineExceptionなどのより深刻な例外をキャッチしないことに注意してください。

また、関連するfinallyブロックのtryは実行されません。

https://csharp.2000things.com/2013/08/30/920-a-finally-block-is-not-executed-when-a-corrupted-state-exception-occurs/

他の未処理のappdomain例外については、次を使用できます。

  • _AppDomain.CurrentDomain.UnhandledException_
  • _Application.Current.DispatcherUnhandledException_
  • _TaskScheduler.UnobservedTaskException_

(特定のハンドラーが状況に適している場合は、詳細を検索してください。たとえば、_TaskScheduler.UnobservedTaskException_は少し注意が必要です。)

Mainメソッドにアクセスできない場合は、AppDomain例外ハンドラーをマークしてCSEをキャッチすることもできます。

_AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

...

[HandleProcessCorruptedStateExceptions, SecurityCritical]
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    // AccessViolationExceptions will get caught here but you cannot stop
    // the termination of the process if e.IsTerminating is true.
}
_

最後の防衛線は、次のようなアンマネージUnhandledExceptionFilterです。

_[DllImport("kernel32"), SuppressUnmanagedCodeSecurity]
private static extern int SetUnhandledExceptionFilter(Callback cb);
// This has to be an own non generic delegate because generic delegates cannot be marshalled to unmanaged code.
private delegate uint Callback(IntPtr ptrToExceptionInfo);
_

そして、あなたのプロセスの最初のどこかで:

_SetUnhandledExceptionFilter(ptrToExceptionInfo =>
{
    var errorCode = "0x" + Marshal.GetExceptionCode().ToString("x2");
    ...
    return 1;
});
_

可能な戻りコードの詳細については、以下を参照してください。

https://msdn.Microsoft.com/en-us/library/ms680634(VS.85).aspx

UnhandledExceptionFilterの「特技」は、デバッガーが接続されている場合は呼び出されないことです。 (少なくとも、WPFアプリを使用している場合はそうではありません)。

上から適切なExceptionHandlersをすべて設定した場合、ログに記録できるすべての例外をログに記録する必要があります。より深刻な例外(StackOverflowExceptionExecutionEngineExceptionなど)の場合、プロセス全体が発生すると使用できなくなるため、別の方法を見つける必要があります。考えられる方法は、メインプロセスを監視して致命的なエラーをログに記録する別のプロセスである可能性があります。

追加のヒント:

26
haindl

ハンドラメソッドを[HandleProcessCorruptedStateExceptions]で装飾することもできることを指摘してくれた@haindlに感謝1 属性なので、実際に想定どおりに機能するかどうかを確認するためだけに、小さなテストアプリを作成しました。

1 注:ほとんどの回答では、[SecurityCritical]属性も含める必要があると記載されていますが、以下のテストでは省略しても動作は変わりませんでした( [HandleProcessCorruptedStateExceptions]だけでも問題なく動作するように見えました)。しかし、私はこれらの人々すべてが彼らが言っていることを知っていたと推定しているので、両方の属性を以下に残します。これは、「StackOverflowからコピーした」パターンの実際の学校の例です。

アイデアは、明らかに、remove<legacyCorruptedStateExceptionsPolicy>app.configからの設定です。つまり、最も外側の(エントリレベル)のみを許可します例外をキャッチしてログに記録し、失敗するハンドラ。設定を追加すると、内部ハンドラーで例外をキャッチし、これが希望どおりではない場合の場合、アプリは続行できます。正確な例外情報を取得し、惨めに死ぬ。

次のメソッド を使用して例外をスローしました:

static void DoSomeAccessViolation()
{
    // if you have any questions about why this throws,
    // the answer is "42", of course

    var ptr = new IntPtr(42);
    Marshal.StructureToPtr(42, ptr, true);
}

1。 Mainからの例外のキャッチ

[SecurityCritical]
[HandleProcessCorruptedStateExceptions]
static void Main(string[] args)
{
    try
    {
        DoSomeAccessViolation();
    }
    catch (Exception ex)
    {
        // this will catch all CSEs in the main thread
        Log(ex);
    }
}

2。バックグラウンドスレッド/タスクを含むすべての例外のキャッチ:

// no need to add attributes here
static void Main(string[] args)
{
    AppDomain.CurrentDomain.UnhandledException += UnhandledException;

    // throw on a background thread
    var t = new Task(DoSomeAccessViolation);
    t.Start();
    t.Wait();
}

// but it's important that this method is marked
[SecurityCritical]
[HandleProcessCorruptedStateExceptions]
private static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    // this will catch all unhandled exceptions, including CSEs
    Log(e.ExceptionObject as Exception);
}

後者の方法のみを使用して、[HandleProcessCorruptedStateExceptions]他のすべての場所から削除して、例外がキャッチされないようにすることをお勧めします間違った場所。つまりtry/catchブロックがどこかにあり、AccessViolationExceptionがスローされた場合、アプリを終了する前に、CLRでcatchブロックをスキップしてUnhandledExceptionに伝達する必要があります。

11
Lou