.NET Fx:Load ハンドラの中で Let's Close!
- 調査のきっかけ
- 注意事項
- 調査環境
- 検証用プログラムの概要
- Form.Close() の実装
- ウィンドウハンドルが作成されるまでの流れ
- Load イベントが発生するまで
- Let's Close!
- まとめ
調査のきっかけ
Form.Load イベントハンドラの中で、Form.Close メソッドを呼び出しても大丈夫なのかと疑問に思ったので調べてみました。
注意事項
説明の内容が正しい保証は一切ありません。
Microsoft が公式に文書化していない内容を前提としてプログラムを書くことは推奨されません。
調査環境
- Windows11 22H2 (OS build 22621.819)
- .NET Framework 4.8.1
検証用プログラムの概要
- 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() の実装は以下のようになっています。
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)
- (1) で ShowWindow() が呼ばれる
- (2) でコントロールが作成される
- (3) の Control.OnCreateControl メソッドは、オーバーライドすることで、コントロールの作成が完了した時に呼び出される
- (4) で Form.Load イベントが発生し、ユーザーコードのイベントハンドラ (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() の中でdialogResult
にDialogResult.Cancel
が設定される - (4) で
dialogResult != DialogResult.None
&&CalledClosing == false
の場合(この時点では true)、(5) のForm.OnFormClosed メソッドが呼ばれ、ユーザーコードのイベントハンドラ (6) が実行される。その後、(4) の中でCalledClosing
にtrue
が設定される
ここで話は ShowDialog のメッセージループの中に移ります。
- (7) の FContinueMessageLoop() メソッド は、メッセージループを継続する場合は
true
それ以外の場合はfalse
を返す - (8) で
closingOnly == false
&&dialogResult != 0
なら、(9) が実行される。(3) でdialogResult
はDialogResult.Cancel
に設定されているので、結果的に (9) の Form.OnFormClosed メソッドが呼ばれ、ユーザーコードのイベントハンドラ (10) が実行される - (9) の Form.OnFormClosed メソッドが実行されると (11) の FContinueMessageLoop() メソッド が
false
を返し、ShowDialog のメッセージループが終了する - Form.ShowDialog(owner:null) の最後で ウィンドウハンドルが破棄される
まとめ
- Control.HandleCreated イベントハンドラの中で、Form.Close を呼び出してはいけない。
InvalidOperationException
例外が発生する - Form.Load イベントハンドラが呼ばれるタイミングでは、ウィンドウハンドルの作成が完了している
- Form.Load イベントハンドラの中で Form.Close を呼び出すと、FormClosing イベント、FormClosed イベント が順に発生し、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) { // ここに実験コードを書く } }
調査環境
- Windows11 22H2 (OS build 22621.819)
- .NET Framework 4.8.1
実験1:同一フォームを2回モーダル表示する
form2.ShowDialog(); // オーナーの指定有無による挙動の違いはなかった
一度目の ShowDialog() は問題なくフォームが表示されるが、二度目の ShowDialog() の呼び出しで System.InvalidOperation
例外が発生した。
例外メッセージは「既に表示されているフォームをモーダルダイアログボックスとして表示できません。ShowDialog を呼び出す前に、フォームの Visible プロパティを false にしてください」という内容。
例外はこの場所でスローされている。
参考
実験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 にしているだけだった。
一方で、オーナーを指定した場合はどうなるか。
form2.Show(this);
この結果は・・・二度目の Show(this) の呼び出しで、実験1(同一フォームを2回モーダル表示する)と同じ例外が発生した。
参考
実験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"); // オーナーの指定有無による挙動の違いはなかった
メッセージボックスが複数個表示された。
参考
まとめ
実験した範囲内で例外が発生するのは以下のケースだった。
- すでにモーダル表示されているフォームを、再度モーダル表示しようとした場合
- オーナーを指定してすでにモードレス表示されているフォームを、再度モードレス表示しようとした場合(※オーナーを指定しない場合は例外は発生しない)
ウィンドウメッセージを覗く (Spy++)
Spy++は、「特定のウィンドウに対して、どのようなメッセージが、どのような順番でWindowsから送信されているか」を調査する際に大変便利なユーティリティ。
インストール
- 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
形式のファイルに変換する。ファイル形式の変換には Microsoft が Github で公開している etl2pcapng というコマンドラインツールを利用する。
以下から単体の実行ファイルがダウンロードできる。
https://github.com/microsoft/etl2pcapng/releases
使い方は以下のように簡単。
etl2pcapng trace.etl trace.pcapng
管理共有
以下の共有がデフォルトで作成される。
共有名 | リソース |
---|---|
ドライブ名 + "$" (例:C$, D$) | C:\ など |
ADMIN$ | %SystemRoot% (C:\WINDOWS) |
共有は net share
で確認できる。
管理共有を無効にする
レジストリで以下の設定をし、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) のハッシュ値 - アプリは低い特権で、制約の多いアプリコンテナ―内で実行される
- アプリのアクティブ化(起動)とライフサイクル(起動から終了まで)は、Windows の
Process 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
カレントスレッドの TIDt0 - 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)
レジスタ
メモリ
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 の情報を表示する
スレッド
メモリ
!address [-summary]
プロセスが使用するメモリの情報を表示する!address Address
Address がどのようなメモリ領域なのか表示する。出力結果のUsage:
行を確認する!heap
スタック
!uniqstack
全てのスレッドのスタックを重複を除去して表示する
ロック
!locks
プロセス内のロックされているクリティカルセクションを一覧表示する
ハンドル
!htrace
ハンドルの Open / Close を実行するすべての呼び出しをスタックトレース付きで監視する OS の機能を有効にする!htrace -enable
ハンドルトレースを有効にし、-diff オプションによって初期状態として使用するハンドル情報の最初のスナップショットを取得する!htrace -diff
取得したハンドル情報の最後のスナップショットと比較し、削除のスタックがないスタックトレースだけを表示する!htrace -disable
ハンドルトレースを無効にする
!handle [Handle] [Flag] [TypeName]
プロセスが所有するハンドルに関する情報を表示するHandle
:0
を指定するか、省略すると全てのハンドルが表示される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
型情報
!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 - スレッドの一覧をスタックトレース付きで表示する