2022年9月12日月曜日

ASP.NET MVC "Cannot perform runtime binding on a null reference"

これは初見の人にとっては厄介な問題です。

 
[RuntimeBinderException: Cannot perform runtime binding on a null reference]
   CallSite.Target(Closure , CallSite , Object ) +148
   System.Dynamic.UpdateDelegates.UpdateAndExecute1(CallSite site, T0 arg0) +664 
   ...
 
初見の人にとって厄介な理由は、修正すべき場所とは異なる場所が指摘されるからです。
 
この例については…
luckyNumber はきちんと初期化しています。
luckyNumber に問題が無い事は明らかです。
 
 
未初期化のフィールドへアクセスしていないかどうかを確認しましょう。
 
この場合は @Model へのアクセスが問題です。
@Model を一切初期化していないのにも関わらず、
@Model へアクセスしようとしています。
 

@Model については @model 宣言をしない場合 dynamic 型として解決されるようです。
dynamic 型は実行時に解決されます。
実行するまでエラーが発生しないため、見過ごしてしまう事が懸念されます。

他のソースコードから input タグ等をコピーしてきた場合に起こり得ます。
@Model は存在しないので value 属性ごと削除しても良いでしょう。


2022年6月1日水曜日

Debugging Tools for Windows の cdb.exe を使って .NET Framework 4.0 アプリのクラッシュダンプから stack trace を得る

Debugging Tools for Windows の cdb.exe を使って .NET Framework 4.0 アプリのクラッシュダンプから stack trace を得る

 

スタックトレースだけを取得したい場合、cdb.exe をワンライナーで使う手もあると思います。

 

cdb.exe は「Windows 用デバッグ ツール (WinDbg)」セットアップに含まれています。

Windows 用デバッグ ツールのダウンロード を参照して、入手してください。

 

X64 Debuggers And Tools-x64_en-us.msi を既定の設定でセットアップした場合:

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"

X86 Debuggers And Tools-x86_en-us.msi を既定の設定でセットアップした場合:

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe"


参考: CDB のコマンドライン オプション

コマンドライン 64-bit 用:

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" -z "C:\ProgramData\Wazato\Wazato.exe.21264.dmp" -aC:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll -lines -c "!sos.printexception -nested -lines" < nul

コマンドライン 32-bit 用 :

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe" -z "C:\ProgramData\Wazato\Wazato.exe.21264.dmp" -aC:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll -lines -c "!sos.printexception -nested -lines" < nul

出力:

Microsoft (R) Windows Debugger Version 10.0.22621.1 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.


Loading Dump File [C:\ProgramData\Wazato\Wazato.exe.24684.dmp]
User Mini Dump File: Only registers, stack and portions of memory are available

Symbol search path is: srv*
Executable search path is:
Windows 10 Version 19044 MP (4 procs) Free x64
Product: WinNt, suite: SingleUserTS
Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
Machine Name:
Debug session time: Wed Jun  1 21:56:33.000 2022 (UTC + 9:00)
System Uptime: not available
Process Uptime: 0 days 0:00:04.000
...................................
Loading unloaded module list
.
----------------------------------------------------------------------------
The user dump currently examined is a minidump. Consequently, only a subset
of sos.dll functionality will be available. If needed, attaching to the live
process or debugging a full dump will allow access to sos.dll's full feature
set.
To create a full user dump use the command: .dump /ma <filename>
----------------------------------------------------------------------------
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(606c.4a64): CLR exception - code e0434352 (first/second chance not available)
For analysis of this file, run !analyze -v
ntdll!NtWaitForMultipleObjects+0x14:
00007ff8`85f4d894 c3              ret
0:000> cdb: Reading initial command '!sos.printexception -nested -lines'
----------------------------------------------------------------------------
The user dump currently examined is a minidump. Consequently, only a subset
of sos.dll functionality will be available. If needed, attaching to the live
process or debugging a full dump will allow access to sos.dll's full feature
set.
To create a full user dump use the command: .dump /ma <filename>
----------------------------------------------------------------------------
Exception object: 0000000002f28178
Exception type:   System.Exception
Message:          例外が発生しました。
InnerException:   Wazato.WazatoException, Use !PrintException 0000000002f28030 to see more.
StackTrace (generated):
*** WARNING: Unable to verify checksum for Wazato.exe
    SP               IP               Function
    0000000000DCCB60 00007FF807220961 Wazato!Wazato.Program.Main(System.String[])+0xd1 [H:\Proj\Wazato\Wazato\Program.cs @ 12]

StackTraceString: <none>
HResult: 80131500

Nested exception -------------------------------------------------------------
Exception object: 0000000002f28030
Exception type:   Wazato.WazatoException
Message:          わざと例外です
InnerException:   <none>
StackTrace (generated):
    SP               IP               Function
    0000000000DCEE90 00007FF8072208FA Wazato!Wazato.Program.Main(System.String[])+0x6a [H:\Proj\Wazato\Wazato\Program.cs @ 17]

StackTraceString: <none>
HResult: 80131500

0:000> NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\atlmfc.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\ObjectiveC.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\concurrency.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\cpp_rest.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\stl.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Data.Json.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Devices.Geolocation.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Devices.Sensors.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Media.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\windows.natvis'
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\winrt.natvis'

 

上記の朱色の部分が、関心の高い部分です。スタックトレースが含まれています。

ただ、一部、行数目 (Program.cs @ 12) の特定に不正確さが見られます。


ソースコード:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Wazato
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            try
            {
                throw new WazatoException();
            }
            catch (Exception ex)
            {
                throw new Exception("例外が発生しました。", ex);
            }
        }
    }

    public class WazatoException : Exception
    {
        public WazatoException() : base("わざと例外です")
        {

        }
    }
}


因みに、普通に実行した場合のエラー出力はこのようなものです。こちらの行数目の特定は正確です:

H:\Proj\Wazato\Wazato\bin\Debug\Wazato.exe

ハンドルされていない例外: System.Exception: 例外が発生しました。 ---> Wazato.WazatoException: わざと例外です
   場所 Wazato.Program.Main(String[] args) 場所 H:\Proj\Wazato\Wazato\Program.cs:行 17
   --- 内部例外スタック トレースの終わり ---
   場所 Wazato.Program.Main(String[] args) 場所 H:\Proj\Wazato\Wazato\Program.cs:行 21

 

そもそも、アプリがクラッシュする前であれば $exception.ToString() でスタックトレースを取得できます。


dobon.net で紹介されている「AppDomain.UnhandledExceptionイベントを使用する方法
」を用いて、アプリがクラッシュする前に、スタックトレースをどこかへ保存する、という手もあると思いました。

世間のクラッシュ情報回収系ツール (App Center Crashes など) では、このような手法を用いているものと思われます。

 

イベントビューアでは (Application → Windows Error Reporting):

障害バケット 、種類 0
イベント名: CLR20r3
応答: 使用不可
Cab ID: 0

問題の署名:
P1: Wazato.exe
P2: 1.0.0.0
P3: c9a37b6c
P4: Wazato
P5: 1.0.0.0
P6: c9a37b6c
P7: 1
P8: 14
P9: System.Exception
P10:

添付ファイル:
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERD9ED.tmp.mdmp
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERDA8A.tmp.WERInternalMetadata.xml
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERDA9B.tmp.xml
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERDAA8.tmp.csv
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERDAD8.tmp.txt

これらのファイルは次の場所にある可能性があります:
\\?\C:\ProgramData\Microsoft\Windows\WER\ReportArchive\AppCrash_Wazato.exe_90677cdd904a7f2b1a386721e7dac5b230783d62_abbc5ab9_2f417abf-d27d-48a1-8f57-3d9dffd9b30e

分析記号:
解決策を再確認中: 0
レポート ID: 06931fdf-f9c9-4609-8100-1ca1b16625a5
レポートの状態: 97
ハッシュされたバケット:
Cab GUID: 0

 

イベントビューアでは (Application → Application Error):

障害が発生しているアプリケーション名: Wazato.exe、バージョン: 1.0.0.0、タイム スタンプ: 0xc9a37b6c
障害が発生しているモジュール名: KERNELBASE.dll、バージョン: 10.0.19041.1706、タイム スタンプ: 0x458acb5b
例外コード: 0xe0434352
障害オフセット: 0x0000000000034fd9
障害が発生しているプロセス ID: 0x6df0
障害が発生しているアプリケーションの開始時刻: 0x01d875b84b1582d2
障害が発生しているアプリケーション パス: H:\Proj\Wazato\Wazato\bin\Debug\Wazato.exe
障害が発生しているモジュール パス: C:\WINDOWS\System32\KERNELBASE.dll
レポート ID: 06931fdf-f9c9-4609-8100-1ca1b16625a5
障害が発生しているパッケージの完全な名前:
障害が発生しているパッケージに関連するアプリケーション ID: 

 

イベントビューアでは (Application → .NET Runtime):

アプリケーション:Wazato.exe
フレームワークのバージョン:v4.0.30319
説明: ハンドルされない例外のため、プロセスが中止されました。
例外情報:Wazato.WazatoException
   場所 Wazato.Program.Main(System.String[])

例外情報:System.Exception
   場所 Wazato.Program.Main(System.String[])

 

CDB と .NET Framework アプリの CPU アーキテクチャ (x86, x64) が異なる場合は "SOS does not support the current target architecture." が出力されます:

0:000> cdb: Reading initial command '!sos.printexception'
SOS does not support the current target architecture.

 

Visual Studio 2022 で .NET Framework 4.x アプリのクラッシュダンプを開く

 Visual Studio 2022 で .NET Framework 4.x アプリのクラッシュダンプを開く

「ファイル」 →「開く」→「ファイル」

右下のファイルタイプを「ダンプ ファイル (*.dmp; *.mdmp; *.hdmp)」にします。

ミニダンプ・完全ダンプ いずれかを開くと概要が表示されます。

つぎに「診断分析の実行」をクリックします。

すると、あたかもデバッグを始めて、デバッグ・ブレークしたかのような画面になります。これで、スタックトレースが得られます。クラッシュ発生の要因に近づけるのではないでしょうか。

勿論、アプリの継続や実行はできません…

[Shift]+[F9] キーで、クイックウォッチを展開しましょう。

例外の詳細を見たい場合は $exception を評価します。

以下はミニダンプ診断時のものです。


「'mscorlib' のメタデータが無効です。ミニダンプをデバッグする場合は、新しいヒープ付きミニダンプを収集し、式を再度評価することによって、この問題を解決できることがあります。」というエラーが幾多表示されます。

以下は完全ダンプ時のものです。


こちらは評価の表示がきちんとできました。


.NET Framework 4.x アプリのクラッシュダンプを作成したい

.NET Framework 4.x アプリのクラッシュダンプを作成したい

レジストリにキーを追加すると、ダンプファイルが得られるようです。

Windows 10 Pro (version 21H2) で確認しました。

参考記事:

以下のレジストリキーの配下に wazato.exe のような exe ファイル名のキーを作成します:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps

詳細は: ユーザーモード ダンプの収集

上図の状態で .reg ファイルをエクスポートするとこのような感じです:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\wazato.exe]
"DumpCount"=dword:00000003
"DumpType"=dword:00000002
"DumpFolder"=hex(2):25,00,50,00,52,00,4f,00,47,00,52,00,41,00,4d,00,44,00,41,\
  00,54,00,41,00,25,00,5c,00,57,00,61,00,7a,00,61,00,74,00,6f,00,00,00
DumpType:

0 = カスタム
1 = ミニ
2 = 完全

レジストリを設定後 .NET Framework のアプリがクラッシュすると .dmp ファイルができました。

クラッシュすると Windows Error Reporting の画面が出現します。
ここで「キャンセル」をクリックして閉じても .dmp は作成されました。

また…
「デバッグ」を選択して、デバッガーが起動し終わった後、
「プログラムの終了」を選択して、アプリを終了した後にも .dmp は作成されました。

単純な容量比較では…

ミニダンプ 11,842 KB
完全ダンプ 102,691 KB

10 倍の差がありました。throw するだけの簡単なコンソールアプリでしたが。

続きはこちら…