.NET Fx:Load ハンドラの中で Let's Close!

調査のきっかけ

Form.Load イベントハンドラの中で、Form.Close メソッドを呼び出しても大丈夫なのかと疑問に思ったので調べてみました。

注意事項

説明の内容が正しい保証は一切ありません。
Microsoft が公式に文書化していない内容を前提としてプログラムを書くことは推奨されません。

調査環境

検証用プログラムの概要

  • Form1 のボタンがクリックされた時に Form2 を生成し、続けて ShowDialog() を呼び出します
  • Form2 の Load イベントの中で Form2 を Close します

各種イベントハンドラブレークポイントを仕掛けて調査しました。

public partial class Form1 : Form
{
    private void button1_Click(object sender, EventArgs e)
    {
        new Form2().ShowDialog();
    }
}

public partial class Form2 : Form
{
    private void Form2_HandleCreated(object sender, EventArgs e) {}

    protected override void OnCreateControl()
    {
        base.OnCreateControl();
    }

    private void Form2_Load(object sender, EventArgs e)
    {
        this.Close();
    }

    private void Form2_FormClosing(object sender, FormClosingEventArgs e) {}

    private void Form2_FormClosed(object sender, FormClosedEventArgs e) {}
}

Form.Close() の実装

Form.Close() の実装は以下のようになっています。

参考:Form.Close() リファレンス実装

public void Close() {
    if (GetState(STATE_CREATINGHANDLE))
        throw new InvalidOperationException(SR.GetString(SR.ClosingWhileCreatingHandle, "Close"));

    if (IsHandleCreated) {
        closeReason = CloseReason.UserClosing;
        SendMessage(NativeMethods.WM_CLOSE, 0, 0);
    }
    else{
        // MSDN: When a form is closed, all resources created within the object are closed and the form is disposed.
        Dispose();
    }
}

ウィンドウハンドルが作成されるまでの流れ

InvalidOperationException 例外(内容:CreateHandle() の実行中は Close() を呼び出せません)が発生するのは、コントロールの状態変数に STATE_CREATINGHANDLE フラグが立っている時に Close() が呼び出された時のようです。
では、このフラグはいつ設定されて、いつ解除されるのでしょうか。

new Form2().ShowDialog() // (1)
  Forms.Form.ShowDialog()
    Forms.Form.ShowDialog(owner:null) // (A)
      Forms.Control.Handle.get()
        Forms.Form.CreateHandle() // (2)
          Forms.Control.CreateHandle()
            SetState(STATE_CREATINGHANDLE, true) // (3)
              Form.NativeWindow.CreateHandle(cp)
                Forms.UnsafeNativeMethods.CreateWindowEx(...)
                  [...]
Form2_HandleCreated(sender, e) // (4)
            SetState(STATE_CREATINGHANDLE, false) // (5)
  • (1) で Form2 を ShowDialog() する
  • (2) でハンドルの作成が始まる。ハンドルはフォームを new した時ではなく ShowDialog() の中で作成されていた
  • (3) で状態変数に STATE_CREATINGHANDLE フラグが設定される
  • その先で Control.HandleCreated イベントが発生し、ユーザコードのイベントハンドラ (4) が実行される
  • (5) で状態変数の STATE_CREATINGHANDLE フラグが解除される
  • (4) の中で Close() を呼び出したところ、InvalidOperationException 例外が発生した

Load イベントが発生するまで

次に、Form.Load イベントが発生するまでの流れを見てみます。

  Forms.Form.ShowDialog(owner:null) // 上のコードの (A) と対応
   [...]
    Forms.Application.RunDialog(form:Form2)
      Forms.Application.ThreadContext.RunMessageLoop(reason:msoloopModalForm, context)
        Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context)
          Forms.Control.Visible.set(true)
            Forms.Form.SetVisibleCore(ture)
              Forms.Control.SetVisibleCore(true)
                SafeNativeMethods.ShowWindow(hwnd, SW_SHOW:0x24) // (1)
                [Unmanaged Code]
                [Mnaged Code]
                Forms.NativeWindow.DebuggableCallback(hWnd, WM_SHOWWINDOW, w, l)
                  Forms.Control.ControlNativeWindow.WndProc(m)
                    Forms.Control.ControlNativeWindow.OnMessage(m)
                      Forms.Form.WndProc(m)
                        Forms.Form.WmShowWindow(m)
                          Forms.ScrollableControl.WndProc(m)
                            Forms.Control.WndProc(m)
                              Forms.Control.WmShowWindow(m)
                                Forms.Control.CreateControl() // (2)
                                  Forms.Control.CreateControl(fIgnoreVisible)
                                    Forms.Form.OnCreateControl() // (3)
                                      Forms.Form.OnLoad(e) // (4)
Form2_Load(sender, e) // (5)

Let's Close!

お待ちかねの Form.Load イベントハンドラの中で Close() を呼び出してみます。

Form2_Load(sender, e)
  this.Close() // (1)
    Forms.Form.Close()
      Forms.Control.SendMessage(msg:WM_CLOSE, wparam:0, lparam:0) // (2)
      [...]
        Forms.Form.WmClose(m)
          dialogResult = DialogResult.Cancel // (3)
          Forms.Form.CheckCloseDialog(closingOnly:true) // (4)
            Forms.Form.OnFormClosing(e) // (5)
Form2_FormClosing(sender, e) // (6)
[...]
Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context)
  UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(...)
    UnsafeNativeMethods.IMsoComponent.FContinueMessageLoop(...) // (7)
      Forms.Form.CheckCloseDialog(closingOnly:false) // (8)
        Forms.Form.OnFormClosed(e) // (9)
Form2_FormClosed(sender, e) // (10)
    UnsafeNativeMethods.IMsoComponent.FContinueMessageLoop(...) // (11)
  UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(...)
  Forms.Application.ThreadContext.EndModalMessageLoop(context)
Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context)
  • (1) のロードイベントハンドラの中で Close() を呼び出す
  • (2) で WM_CLOSE メッセージが送信される
  • (3) WM_CLOSE ハンドラ WmClose() の中で dialogResultDialogResult.Cancel が設定される
  • (4)dialogResult != DialogResult.None && CalledClosing == false の場合(この時点では true)、(5)Form.OnFormClosed メソッドが呼ばれ、ユーザーコードのイベントハンドラ (6) が実行される。その後、(4) の中で CalledClosingtrue が設定される

ここで話は ShowDialog のメッセージループの中に移ります。

まとめ

.NET Fx:ShowDialog、Show の実験

定期的に実行されるタイマーイベントハンドラの中で、フォームやメッセージボックスを条件を変えて表示し、その時の挙動を確認した。

実験コードの雛形は以下のようなコード。

public class Form1 : Form
{
    public Form1()
    {
        ...
        this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        // ここに実験コードを書く
    }
}

調査環境

実験1:同一フォームを2回モーダル表示する

form2.ShowDialog(); // オーナーの指定有無による挙動の違いはなかった

一度目の ShowDialog() は問題なくフォームが表示されるが、二度目の ShowDialog() の呼び出しで System.InvalidOperation 例外が発生した。
例外メッセージは「既に表示されているフォームをモーダルダイアログボックスとして表示できません。ShowDialog を呼び出す前に、フォームの Visible プロパティを false にしてください」という内容。

2022-12-07-2250.png

例外はこの場所でスローされている。 2022-12-07-2337.png

参考

Form.ShowDialog() のリファレンス実装

実験2:別フォームを1回ずつモーダル表示する

if (count == 0) {
    count++;
    form2.ShowDialog();
} else if (count == 1) {
    count++;
    form3.ShowDialog();
}

これは問題なく form2 も form3 も表示され、form3 は form2 の上に表示された。
以下のようにオーナーを指定した場合でも挙動に違いはなかった。

if (count == 0) {
    count++;
    form2.ShowDialog(this);
} else if (count == 1) {
    count++;
    form3.ShowDialog(form2);
}

実験3:同一フォームを2回モードレス表示する

form2.Show();

この結果は・・・form2 は 1つしか表示されなかった。
実装は、オーナーを指定しない場合は、Visible = true にしているだけだった。 2022-12-07-2355.png

一方で、オーナーを指定した場合はどうなるか。

form2.Show(this);

この結果は・・・二度目の Show(this) の呼び出しで、実験1(同一フォームを2回モーダル表示する)と同じ例外が発生した。 2022-12-08-0000.png

参考

実験4:別フォームを1回ずつモードレス表示する

まずは、オーナーを指定しないケース。

if (count == 0) {
    count++;
    form2.Show();
} else if (count == 1) {
    count++;
    form3.Show();
}

この結果は・・・問題なく form2、form3 がモードレス表示された。オーナーを指定していないので、Z オーダーの順番は任意に変更できる。

次に、オーナーを指定した場合はどうなるか・・・

if (count == 0) {
    count++;
    form2.Show(this);
} else if (count == 1) {
    count++;
    form3.Show(form2);
}

こちらも問題なく form2, form3 がモードレス表示された。オーナーを指定したことで Z オーダーの順番は form1 < form2 < form3 となった。

実験5:メッセージボックスを複数回表示する

MessageBox.Show("fired"); // オーナーの指定有無による挙動の違いはなかった

メッセージボックスが複数個表示された。 2022-12-09-2041.png

参考

まとめ

実験した範囲内で例外が発生するのは以下のケースだった。

  • すでにモーダル表示されているフォームを、再度モーダル表示しようとした場合
  • オーナーを指定してすでにモードレス表示されているフォームを、再度モードレス表示しようとした場合(※オーナーを指定しない場合は例外は発生しない)

ウィンドウメッセージを覗く (Spy++)

Spy++は、「特定のウィンドウに対して、どのようなメッセージが、どのような順番でWindowsから送信されているか」を調査する際に大変便利なユーティリティ。

2022-12-06-2312.png

インストール

  • Visual Studio を起動し、[ツール] - [ツールと機能を取得] をクリックする
  • [個別のコンポーネント] タブを開き、[デバッグとテスト] セクションの [C++のプロファイルツール] を選択しインストールする
  • インストールが完了したら Visual Studio の [ツール] - [Spy++] から起動できる

メッセージビュー

  • 2列目:ウィンドウハンドル
  • 3列目:メッセージコード(P, S, s, R
  • デコードされたメッセージパラメーターと戻り値は 4列目以降に表示される

メッセージコード

コード 意味
P PostMeesage 関数でメッセージがキューにポストされた(posted)。メッセージの最終的な処理に関する情報はない
S SendMessage 関数でメッセージが送信された(sent)。これは、受信側がメッセージを処理して返すまで、送信側は再度制御できるようにならないことを意味する。 このため、受信側は戻り値を送信側に渡すことができる
s メッセージは送信されたが、セキュリティにより戻り値にアクセスできない
R 各 'S' 行には、対応する 'R' (リターン) 行がある。これは、メッセージの戻り値の一覧を表示する。メッセージの呼び出しが入れ子になっている場合がある。これは、あるメッセージハンドラーが別のメッセージを送信することを意味する

参考

Windows 標準コマンドでパケットキャプチャ

pktmon

Windows の標準コマンド(Windows 10 October 2018 Update 以降) pktmon でパケットキャプチャを取れる。pcapng への変換も別ツールを使わずできて便利。後述の netsh の場合は別途ツールのダウンロードが必要。

フィルタを設定する

追加

MACアドレスIPアドレス、ポートについては送信元と送信先が区別されない

pktmon filter add [name] <filter>

pktmon filter add -i 10.0.0.10 -t icmp
pktmon filter add -p 53
  • -p <port>
  • -i <ip> -i <ip/subnet>
  • -t <tcp|udp|icmp|number>

確認

pktmon filter list

削除

pktmon filter remove

キャプチャを開始する

  • -f <filename> を指定しないと規定値は PktMon.etl となる
  • --pkt-size 0 を指定しないと、各パケットについてパケット全体ではなくパケットの先頭から 128 バイトしか記録されない
pktmon start -c --pkt-size 0 -f <filename>

特定のインターフェースのパケットのみキャプチャする場合、以下のコマンドでインターフェス ID を確認し --comp <ID> で指定する

pktmon list

pktmon start -c --pkt-size 0 --comp <ID> -f <filename>

キャプチャを停止する

pktmon stop

ログファイルを変換する

キャプチャした etl 形式のファイルを、Wireshark で開ける pcapng 形式のファイルに変換する。

pktmon etl2pcap <etl file>

Functionality

Packet Monitor offers the following functionality:

  • Packet monitoring and counting at multiple locations along the networking stack
  • Packets drop detection at multiple stack locations
  • Flexible runtime packet filtering with encapsulation support
  • General logging and tracing support (ETW and WPP events)
  • TXT log analysis based on TcpDump packet parsing
  • Multiple logging modes: real-time, high volume in-memory, multi-file, circular
  • Ethernet, Wi-Fi, and mobile broadband media type support
  • PCAPNG format support

参考

netsh

Windows の標準コマンド netsh でパケットキャプチャを取れる。

キャプチャを開始する

  • traceFile=<filename> を指定しないと既定では %TEMP%NetTraces に出力される
  • maxsize=XXX でキャプチャサイズの上限を指定できる(単位はMB)
netsh trace start capture=yes traceFile=<filename>

OS 起動時のパケットをキャプチャしたい場合は persistent=yes を指定する。

フィルタを設定する

IP アドレス

IPv4.Address=192.168.0.10
IPv4.Address=!(192.168.0.10)
IPv4.Address=(192.168.0.10,192.168.0.20)

インターフェース

netsh trace show interfaces
netsh trace start capture=yes CaptureInterface={XXXX-XXXX-XXXX-XXXX-XXXXXXXX}

ヘルプ

netsh trace show capturefilterHelp

キャプチャを停止する

netsh trace stop

ログファイルを変換する

キャプチャした etl 形式のファイルを、Wireshark で開ける pcapng 形式のファイルに変換する。ファイル形式の変換には MicrosoftGithub で公開している etl2pcapng というコマンドラインツールを利用する。

以下から単体の実行ファイルがダウンロードできる。
https://github.com/microsoft/etl2pcapng/releases

使い方は以下のように簡単。

etl2pcapng trace.etl trace.pcapng

管理共有

以下の共有がデフォルトで作成される。

共有名 リソース
ドライブ名 + "$" (例:C$, D$) C:\ など
ADMIN$ %SystemRoot% (C:\WINDOWS)

共有は net share で確認できる。

2022-11-20-1334.png

管理共有を無効にする

レジストリで以下の設定をし、PC を再起動する。

キー : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\LanmanServer\Parameters
名前 : AutoShareWks または AutoShareServer (種類:REG_DWORD)
データ : 0 で管理共有を無効化、1 で管理共有を有効化

参考

UWP アプリってなんだ

UWPアプリ

  • UWP アプリとは Universal Windows Platform で動くアプリのこと
  • 基本的に Microsoft Store から入手する
  • WinRT (Windows Runtime) API を利用する
  • アプリは Microsoft.SysinternalsSuite_2022.11.1.0_x64__8wekyb3d8bbwe のような AppX パッケージの名前で識別される。末尾のハッシュ値は、アプリの発行者ID (PublisherId) のハッシュ値
  • アプリは低い特権で、制約の多いアプリコンテナ―内で実行される
  • アプリのアクティブ化(起動)とライフサイクル(起動から終了まで)は、WindowsProcess Lifetime Management (PLM) によって制御され、アプリは Windows Runtime (WinRT) サブシステム内で実行される

AppExecutionAlias

  • AppExecutionAlias は Windows 10 Fall Creators Update(バージョン1709)から導入された
  • 環境変数の PATH に %USERPROFILE%\AppData\Local\Microsoft\WindowsApps が設定されている
  • %USERPROFILE%\AppData\Local\Microsoft\WindowsApps にサイズ 0 の実行ファイル名のファイルが存在する(Ex. notepad.exe など)。これは単純なショートカットではなく、アクティブ化(起動)に必要な情報(アプリの PackageFamilyName や ApplicationID など)を提供している

UWP アプリをコマンドで起動する

PowerShell で以下のコマンドを実行する。

$app = Get-AppxPackage -Name *<Search Word>*
$package = $app | Get-AppxPackageManifest
$app.PackageFamilyName
$package.Package.Applications.Application.Id
  • 3番目のコマンドで PackageFamilyName が取得できる
  • 4番目のコマンドで ApplicationID が取得できる

あるいは以下のように実行しても取得できる。

(Get-AppxPackage -Name *<Search Word>*).PackageFamilyName
(Get-AppxPackage -Name *<Search Word>* | Get-AppxPackageManifest).package.applications.application.id

上記で取得した情報を使って PowerShell から以下のコマンドで UWP アプリを起動できる。

Start-Process shell:AppsFolder\<PackageFamilyName>!<AppID>

UWP アプリのメモ帳を起動する例

$app = Get-AppxPackage -Name *notepad*
$package = $app | Get-AppxPackageManifest
$app.PackageFamilyName
$package.Package.Applications.Application.Id

Start-Process shell:AppsFolder\Microsoft.WindowsNotepad_8wekyb3d8bbwe!App

参考

WinDbg コマンド

随時更新予定です。

疑似レジスタ

  • $ip
    命令ポインタレジスタx86: eip x64: rip. も現在の命令ポインタの値に評価される
  • $ra
    現在の関数からのリターンアドレス
  • $retreg
    関数戻り値。x86: eax x64: rax
  • $csp
    現在のスタックポインタ。x86: esp x64: rsp
  • $proc
    カレントプロセス。ユーザーモードデバッガでは PEB のアドレス。カーネルモードデバッガではカレントプロセスの EPROCESS 構造体のアドレス
  • $thread
    カレントスレッド。ユーザーモードデバッガでは TEB のアドレス。カーネルモードデバッガではカレントスレッドの ETHREAD 構造体のアドレス
  • $tpid
    カレントプロセスの PID
  • $tid
    カレントスレッドの TID
  • t0 - t19
    20個の汎用疑似レジスタ

標準コマンド

スレッド

  • ~~[TID]s
    スレッド ID が TID であるスレッドにコンテキストを切り替える

ブレークポイント

  • [~Thread] bp Address ["CommandString"]
    Address にブレークポイントを設定する。スレッドを指定した場合、そのスレッドの時にのみ停止する。ブレークポイントに達する度に実行されるコマンドを指定できる。複数のコマンドを指定する場合はセミコロンで区切る
  • [~Thread] bm Module!Symbol ["CommandString"]
    Symbol にブレークポイントを設定する
  • [~Thread] bu Module!Symbol ["CommandString"]
    まだロードされていないモジュールに(既にロードされていてもよい)遅延ブレークポイントを設定する
  • bl
    ブレークポイントの一覧を表示する
  • bc ID [,ID ...]
    ID のブレークポイントを削除する
  • bd ID [,ID ...]
    ID のブレークポイントを無効にする
  • be ID [,ID ...]
    ID のブレークポイントを有効にする
  • ba <Access> <Size> <Address>
    指定したアドレスにアクセス時に中断する。Access には r: 読み取りまたは書き込み w: 書き込み e: 命令を取得 が指定できる

ステップ実行

  • p
    ステップオーバー。1行実行する。関数があった場合関数を実行して次の行に進む。関数の中に入らず飛ばすので「オーバー」
  • t
    ステップイン。デバッガ―がソースモード l+t の時は単一のソース行、アセンブリモード l-t の時は単一のアセンブリ命令を実行する。プライベートシンボルがない場合はアセンブリモードしか使えない
  • g <Address>
    指定されたアドレスに達するまで実行する
  • gu
    現在の関数が完了するまで実行する
  • pc
    次の関数呼び出しまで実行する

変数の表示

  • dv [Pattern]
    パターンが指定された場合、指定したパターンに一致するローカル変数のみをコマンドに表示する
  • dv /i
    シンボルのタイプと変数のタイプも表示する
  • dv /V
    変数が格納されている位置を表示する

型の表示

  • dt ntdll!_HEAP_ENTRY Address
    Address の ntdll モジュールの _HEAP_ENTRY 構造体の内容を表示する

シンボル

  • x module!symbol
    Symbol のアドレスを表示する
  • ln <address>
    指定されたアドレスのまたはその近くにあるシンボルを表示する

アセンブル (unassembly)

  • ub eip L20
    eip のアドレスから逆向きに 20行逆アセンブルする
  • uf <address>
    指定されたアドレスを含む関数全体を逆アセンブルする

レジスタ

メモリ

  • d (メモリの表示)
    • db Address
      バイト値と ASCII 文字
    • dc Address
      4バイト値と ASCII 文字
    • dd Address
      4バイト値
    • du Address
      Unicode 文字列

スタックトレース

  • kp
    スタックトレースで呼び出される各関数のすべてのパラメーターを表示する。完全なシンボル情報が必要
  • kP
    kp と異なるのは 1つのパラメーターにつき 1行を使って表示する
  • kb
    関数に渡された最初の 4つのパラメータを表示する
  • kf
    関数が使ったスタックサイズを最初のカラムに表示する

便利コマンド

  • ? Num
    Num の 10進数、16進数を変換できる
  • * comment
    コメントが書ける
  • version
    デバッガを実行しているコンピュータの OS のバージョン情報、デバッガーおよび読み込まれているすべての拡張 dll に関するバージョン情報を表示する
  • vertarget
    デバッガを実行しているコンピュータの OS のバージョン情報を表示する

メタコマンド (dot コマンド)

シンボル

  • .reload
    ロードされているすべてのモジュールのシンボル情報を破棄し、デバッガを初期状態に戻す

ソース

  • .srcpath[+] [directory [; ...]] ソースファイルの検索パスを設定または表示する
  • .srcnoisy [0|1]
    ソースファイルの読み込みの詳細レベルを制御する

ログ

  • .logopen /t filename
    デバッガ―での操作・出力をログを filename に記録する
  • .logclose
    開いているログファイルをクローズする

例外

  • .lastevent
    デバッガでプログラムが現在停止している原因となった直近のデバッガイベントに関する情報を表示する
  • .exr -1
    最新の例外レコードの内容を表示する
  • .ecxr
    現在の例外に関連付けられているコンテキストレコードを表示する

拡張機能

  • .chain
    読み込まれたすべてのデバッガー拡張機能を既定の検索順序で一覧表示する

ヘルプ

  • .hh <text>
    HTML ヘルプファイルを開く

拡張コマンド (bang コマンド)

シンボル

  • !sym [noisy|quiet]
    .reload コマンドの詳細ログのオン・オフを切り替える
  • !chksym <module>
    ロード済みのシンボルがイメージに対して正しいかチェックする

モジュール

  • !lmi <Module>
    モジュールに関する詳細情報を表示する
  • !dh <module address>
    モジュールのヘッダー情報を表示する

プロセス

  • !peb
    プロセス環境ブロック PEB の情報を表示する

スレッド

  • !teb
    スレッド環境ブロック TEB の情報を表示する
  • !gle
    現在のスレッドの最後のエラー値を表示する。API がエラーを起こした直後に使用する

メモリ

  • !address [-summary]
    プロセスが使用するメモリの情報を表示する
  • !address Address
    Address がどのようなメモリ領域なのか表示する。出力結果の Usage: 行を確認する
  • !heap
    • !heap -s
      ヒープのサマリ情報を表示する
    • !heap -a Address
      指定したヒープのヒープブロックのリストを全て表示する
    • !heap -l
      GC アルゴリズムを使用して、プロセス内のどこからも参照されていないアクティブなアロケーションを検出する
    • !heap -x Address
      指定したアドレスを含むヒープブロックを検索し、ヒープの使用状況を表示する

スタック

  • !uniqstack
    全てのスレッドのスタックを重複を除去して表示する

ロック

ハンドル

  • !htrace
    ハンドルの Open / Close を実行するすべての呼び出しをスタックトレース付きで監視する OS の機能を有効にする

    • !htrace -enable
      ハンドルトレースを有効にし、-diff オプションによって初期状態として使用するハンドル情報の最初のスナップショットを取得する
    • !htrace -diff
      取得したハンドル情報の最後のスナップショットと比較し、削除のスタックがないスタックトレースだけを表示する
    • !htrace -disable
      ハンドルトレースを無効にする
  • !handle [Handle] [Flag] [TypeName]
    プロセスが所有するハンドルに関する情報を表示する

    • Handle0 を指定するか、省略すると全てのハンドルが表示される
    • Flag:表示する情報を指定し、f が一番情報量が多い
    • TypeName:表示するハンドルの種類を指定する。Event、Section、File、Port、Directory、SymbolicLink、Symbolic、WindowStation、Semaphore、Key、Token、Process、Thread、Desktop、IoCompletion、Timer、Job、WaitablePort が指定できる

SOS (Son Of Strike)

SOS.dll (SOS debugging extension)

SOS.dll は、.NET Framework に含まれている拡張機能 DLL。.NET Framework の各バージョン、各プラットフォーム (32/64-bit) 毎に存在する。デバッグ対象のプログラムと同じ .NET Framework のバージョン、同じプラットフォームの SOS.dll をロードする必要がある。

SOS.dll のロード

  • sxe ld:clrjit; g
    clrjit.dll がロードされるまで実行してブレークする
  • .loadby sos clrgit
    clrjit.dll と同じところにある SOS をロードする

ヘルプ

  • !sos.help [<command>]
    コマンドについての詳細なヘルプを表示する

スレッド

  • !threads
    プロセス内のすべてのマネージド スレッドを表示する。先頭の ID は WindDbg 上でのスレッドID、2番目の ID は CLR のスレッド ID、3番目は OS のスレッドID。

ヒープ

  • !dumpheap
    • !dumpheap -stat
      ヒープの使用状況(型ごとのオブジェクトの数とその総サイズの)を表示する
    • !dumpheap -live -mt <MT addr>
      MethodTable(型)から生きているオブジェクトの一覧を出力する
  • !gcroot <Object address>
    指定したオブジェクトの参照元を表示する

スタック

  • !clrstack [-a][-p][-l]
    マネージコードのみのスタックを表示する。-a: -l と -p 両方 p: 引数を表示 l: ローカル変数を表示
  • !sos.dso
    現在のスタックの範囲内で見つかったすべてのマネージドオブジェクトを表示する
  • !eestack [-EE]
    プロセス内の全てのスレッドのスタックトレースを表示する。-EE オプションを付けるとマネージコードのみ表示する。

例外

  • !pe
    • !pe [<Exception object address>]
      指定したアドレスの例外情報を表示する。アドレスが指定されていない場合、現在のスレッドで最後にスローされた例外が表示される。
    • !pe -nested [<Exception object address>]
      内部例外の詳細を表示する

ブレークポイント

  • !bpmd
    • !bpmd -md <MethodDesc>
    • !bpmd <module name> <class name>.<method name>
      まだ JIT されていないメソッドにブレークポイントを設定できる

型情報

  • !dumpmt
    • !dumpmt <MethodTable address>
      型情報を出力する
    • !dumpmt -md <MethodTable address>
      オブジェクトに定義されているすべてのメソッドの一覧も表示される
  • !dumpclass <EEClass address>
    EEClass 構造体に関する情報を表示する

ロック

  • !syncblk -all
    スレッドが所有している SyncBlock 構造体の情報を表示する。SyncBlock は .Net のロック実装に関係する。

モジュール

  • !savemodule <Base address> <Filename>
    メモリ内の指定したアドレスに読み込まれているイメージを指定したファイルに書き込む

MEX (Managed EXtension)

MEX Debugging Extension for WinDbg

  • .load <path to mex.dll>
    mex.dll をロードする
  • !mex.help
    カテゴリ毎に分類されたヘルプが表示される
  • !mex.di
    ダンプ情報を表示する
  • !grep <keyword> !mex.help -all
    MEX のコマンドで <keyword> を含むコマンドを検索する
  • !tasklist [-cpu]
    プロセスの一覧を表示する
  • !us
    ユニークなスタック一覧を表示する
  • !lt
    スレッドの一覧を表示する
  • !mex.us
    同じスレッドスタックをグルーピングしてユニークなスレッドスタックの一覧を表示する

TTD (Time Travel Debugging)

Time Travel Debugging - Overview

  • g-
    逆方向に実行する

Kernel-Mode

メタコマンド

  • .trap [Address]
    トラップフレームの情報を表示する

拡張コマンド

  • !process 0 Flags XXX.exe
    • Flags: 4 - スレッドの一覧を表示する
    • Flags: 6 - スレッドの一覧をスタックトレース付きで表示する

参考