ホーム  ざれごと  ワシントン州  ツール  NT豆知識  Win32プログラミングノート  私的用語  ジョーク  いろいろ  ゲーム雑記  Favorites  掲示板   Mail

NTネイティブなデバッガ

Last modified: Sat Apr 05 04:42:12 2003 PDT

一つ上へ


NTネイティブなデバッガのテクニック。 デバッガは日々変更されつつあるので、ここで紹介したテクニックが使えなかったらごめん、ということで。
現在、おもいっきり工事中

ntsd と kd

ntsd(あるいはcdb)は、ユーザモードで動作するデバッガです。 一方、kd はカーネルモードデバッガ。システムのほとんど全ての機能をデバッグすることができます。

別の言い方をすると、ntsd はアプリケーションデベロッパのためのデバッガ。 kd はシステムデベロッパ(ドライバなど)のためのデバッガ、とでも言えるでしょう。

ここでは、 MSDN などのような網羅的な解説は行いません。 知っていると便利、デバッグ生活をちょっと楽にしてくれるような、小手先テクニックを紹介していきます。

基本コマンド

スタックトレースの表示

スタックトレースの表示は、基本中の基本でしょう。

k

スタックトレース表示のコマンドは、 k です。

kb

k に b をつけて kb とすると、引数も含めてスタックトレースを表示します。

kv

k に v をつけて kv とすると、引数、トラップフレームなど詳細情報を含めてスタックトレースを表示します。

レジスタ

r
単に r と叩くと、全てのレジスタを表示し、次に実行するアセンブリラインを表示します。

0:000> r
eax=01a05b40 ebx=25af0d2d ecx=0006fc7c edx=00000000 esi=0006fefc edi=77d43ca4
eip=7ffe0304 esp=0006febc ebp=0006fed8 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000202
*SharedUserSystemCall+c:
7ffe0304 c3               ret

特定のレジスタだけ表示したい場合は、 r <レジスタ名> です。 x86の場合、レジスタ名は次のようになります。

汎用レジスタ

eax, ebx, ecx, edx, esi, edi

インストラクションポインタ

eip

スタック

esp, ebp

セグメントレジスタ

cs, ss, ds, es, fs, gs

r <レジスタ名>=値
で、レジスタの値を変更することもできます。

メモリダンプ

d コマンドです。タイプを後ろにくっつけられます。
d[type] [range]
こんな感じ。

0:000> dd 6fefc l4
0006fefc  01080856 00000100 0000007b 00580001
0:000> dc 6fefc l4
0006fefc  01080856 00000100 0000007b 00580001  V.......{.....X.

タイプ

b

byte

w

word

d

dword

ds

dword (or qword for 64bit) with symbols

a

ascii

c

dword and char

u

unicode

q

qword

コンテクストの切り替え

スレッドコンテクストの切り替え(ntsd)

~スレッド番号s
スレッド番号は、 ~ と叩けば一覧が取得可。 プロセスの情報は、 | と叩けばよい。

 0:001> ~
    0  id: 6a8.2e4   Suspend: 1 Teb 7ffde000 Unfrozen
 .  1  id: 6a8.804   Suspend: 1 Teb 7ffdd000 Unfrozen
 0:001> |
 .  0    id: 6a8 create  name: notepad.exe
 

トラップフレーム

.trap <アドレス>

エクセプション

↓のエクセプションの項目を見てください。

ブレークポイント

ソフトウェアブレークポイント

ハードウェアブレークポイント

ba <e|r|w|i> <1|2|4> <アドレス>

たぶんよく使うのは、ba w 4 の形式。 指定されたアドレスに書き込みがなされると、デバッガへ落ちる。

セッションに属するページには、ソフトウェアブレークポイントがしかけられない(kd)。 そのため、ハードウェアブレークポイントを使わなければならない。

呼び出し元へ戻る

g $ra
CPU のタイプに関わらず実行可能。 $ra は、リターンアドレスを示す仮想レジスタ。

型・変数の表示

アドレスのバリデーション

x <シンボル>
ワイルドカードを含むことができる。 相手が関数なら、そのタイプも表示可(Stripされていないシンボルが必要?)。 変数なら、場合によっては型・中身まで表示してくれる。

dtコマンド

lnコマンド

.frame コマンドと dv コマンド

sx

特定のイベント発生時のデバッガブレークのイネーブル・ディスエーブル。 sxe [event] でイネーブル、sxd [event] でディスエーブル。 sx で一覧表示。

ld

モジュールの新規ロード時にブレーク。

行番号の表示

.lines
行番号の表示モードをトグルします。 ちょっとうっとうしいので、 必要以外はオフにしているのが吉かも。

実行をとめる

デバッガから Ctrl+C を叩くと、実行を中断させてデバッガのプロンプトに落ちます。
ntsd では、デバッグ中のアプリケーションをフォアグラウンドに持ってきて F12 を押すと デバッガのプロンプトに落ちます。

イニシャルブレークポイント

ntsd の場合、"-g" オプションをつけなければイニシャルブレークポイントで止まります。 kd の場合、 Ctrl+K で、OS のスタート時に止まるかどうかをトグルします。

ntsd の入出力を kd に通す(ntsd)

当然 ntsd オンリですが、起動時オプションに "-d" をつけると、kd から ntsd を操作可能になります。

すでに実行中のプロセスに ntsd をアタッチする(ntsd)

起動オプションに、"-p <process-id>" を付けます。

最初からやりなおす

ntsd の場合、 .restart で実行を最初からやり直すことができることがあります。 kd で OS をリブートさせるには、 .reboot です。

デバッガエクステンション

!コマンド

エクステンションず

個別のテクニック

デッドロック(あるいはクリティカルセクションタイムアウト)のデバッグ

!critsec (ntsd)、または !locks

エクセプション(例外)の情報を取得

エクセプションから得られる情報には、エクセプションレコードと、コンテクストレコードがあります。 エクセプションレコードには、例外の種別・アクセスしたアドレスなどが収められています。 例外の種類によっては、収められている情報が異なるかもしれません。

コンテクストレコードには、例外が発生したときのCPUのコンテクストが収められています。

デバッガが例外をキャッチしたときには、上記のエクセプションレコード・コンテクストレコードのアドレスを表示してくれると思います。 それらのアドレスを、それぞれ .exr.cxr に渡すことで、カレントコンテクストをセットすることができます。 あとは、r なり kv なりなんなりと。 コンテクストを元に戻す場合には、引数なしで .cxr とタイプします。

例外のキャッチは、sx で設定できます。 ファーストチャンスエクセプションは、例外が最初に起こったとき。 例外ハンドラに処理を渡す前に、デバッガに落ちます。 セカンドチャンスエクセプションは、例外がどのハンドラにも処理されなかったとき。 セカンドチャンスの方はデフォルトでオン、ファーストチャンスはデフォルトでオフだったと思います。

例:

kd> .exr 0x6ff3e8
ExceptionAddress: 75b42ef7 (CSRSRV!CsrUnhandledExceptionFilter)
   ExceptionCode: c0000006
  ExceptionFlags: 00000010
NumberParameters: 3
   Parameter[0]: 00000000
   Parameter[1]: 75b42ef7
   Parameter[2]: c000009a
Inpage operation failed at 75b42ef7, due to I/O error c000009a

kdへ落ちる(ntsd)

.breakin
このコマンドで、ntsd から kd へ落ちます。 ntsd へ戻る場合は、kd のプロンプトから、 g でおっけ。

なお、kd へ落ちたときにはデバッガのコンテクストがカレントなので、必要に応じてコンテクストを切り替えます

ロードされているDLLの表示

!peb で、Process Environment Block の情報が表示されます。ロードされている DLL の一覧も出てきます。 もう少し詳細を知りたい場合は、 !dlls でおっけ。

ロードされているシンボル情報の表示

lml

LastError

!teb
TEB (Thread Environment Block) を表示させると、その中に LastError が格納されています。

バグチェックのデバッグ(kd)

TBD

NTSTATUSの表示

!error エラーコード
を使えば、謎に満ちたステータスコードの正体がわかります。

メモリ違反のデバッグ

TBD

プロセスの列挙(kd)

単にプロセス一覧を見たい場合、
!process 0 0
加えて、非常にconciseなスレッド情報を見るには、 !process 0 4
他に、!stacks とか。

セッションの列挙(kd)

!process 0 0 csrss.exe
Sid で表示されるのが、セッションID。

スレッドの情報取得(kd)

!thread <ETHREADのアドレス>
引数をつけないと、対象はカレントスレッド。

プロセスコンテクストの切り替え(kd)

.process -p <EPROCESSのアドレス>

元のコンテクストに戻る場合は、引数をつけずに .process でおっけ。

スレッドコンテクストの切り替え(kd)

TBD

セッションコンテクストの切り替え(kd)

目的のセッションコンテクストで走っているプロセスを見つけて、プロセスコンテクストを切り替える。

メモリ使用状況の取得(kd)

!vm
基本状況に加え、それぞれのプロセスごとのメモリ使用状況も表示します。 簡単にすませたい場合は、 !vm 1 でおっけ。

メモリ不足

kd> !vm 1

*** Virtual Memory Usage ***
        Physical Memory:     81805   (  327220 Kb)
        Paging File Name paged out
           Current:    491520Kb Free Space:    433312Kb
           Minimum:    491520Kb Maximum:       983040Kb
        Available Pages:       528   (    2112 Kb)
        ResAvail Pages:         89   (     356 Kb)

        ********** Running out of physical memory **********

        Locked IO Pages:       103   (     412 Kb)
        Free System PTEs:    25798   (  103192 Kb)
        Free NP PTEs:         7218   (   28872 Kb)
        Free Special NP:         0   (       0 Kb)
        Modified Pages:       9450   (   37800 Kb)
        NonPagedPool Usage:  24984   (   99936 Kb)
        NonPagedPool Max:    32236   (  128944 Kb)
        PagedPool 0 Usage:    3711   (   14844 Kb)
        PagedPool 1 Usage:     529   (    2116 Kb)
        PagedPool 2 Usage:     519   (    2076 Kb)
        PagedPool Usage:      4759   (   19036 Kb)
        PagedPool Maximum:   64512   (  258048 Kb)
        Shared Commit:        1315   (    5260 Kb)
        Special Pool:            0   (       0 Kb)
        Shared Process:       1955   (    7820 Kb)
        PagedPool Commit:     4759   (   19036 Kb)
        Driver Commit:         822   (    3288 Kb)
        Committed pages:    110319   (  441276 Kb)
        Commit limit:       198282   (  793128 Kb)

プール(kd)

プールの使用状況

!poolused
オプションはヘルプを見よ。

プールタグの検索

!poolfind
オプションはヘルプを見よ。

特定のブロックを含むプールブロックの表示

!pool <アドレス>

プールブロックのバリデーションも行ってくれます(スモールプールの場合)。 なお、ベリファイアでスペシャルプールの指定を行っている場合は、アロケーションの大きさに関わらず1ページ割り当てられるので、びっくりしないでください。

ドライバ・ベリファイア(kd)

!verifier 1
でベリファイアの設定を確認。

論理アドレスの評価(kd)

ユーザモードアドレス

カーネルモード

userexts / userkdx デバッグエクステンション

userexts / userkdx は、ウィンドウマネージャのデバッグエクステンションです。 userexts がユーザモード、userkdx がカーネルモード用。 なおこれらのコマンドを使うには、stripされていないシンボルが必要です。

GUIプロセス・スレッド

GUIプロセス・スレッド情報の表示(userkdx)

!dti, !dt, !dp, !dpi

ウィンドウ

ウィンドウの表示

!dw

詳細表示の例

0: kd> !dw -vf bc6809d8
  s 0  et 0x81e05020 t 0x99e8feb0 q 0xe2080760 ppi 0xb967fe70 i 4d8.7e4 dwwin.exe
pwnd    = 0xbc6809d8
        pti                0x99e8feb0
        handle             0x004501be
        spwndNext          0xbc672d90     ""
        spwndPrev          0x00000000     ""
        spwndParent        0xbc6706e8     ""
        spwndChild         0xbc680ae8     "&Close"
        spwndOwner         0xbc6808e8     "OfficeWatson"
        rcWindow           (110,122)-(529,321) 419x199
        rcClient           (113,151)-(526,318) 413x167
        lpfnWndProc        0x77d67459     (user32!DefDlgProcW) Unicode
        pcls              0xbc677d28     (V):0x8002     (NV):0x8002     Name:"#32770"
        hrgnUpdate         0x00000000
        spwndLastActive    0xbc6809d8     "Microsoft Office"
        ppropList          0xbc680ad0
        pSBInfo            0x00000000
        spmenuSys          0x00000000
        spmenu/id          0x00000000
        hrgnClip           0x00000000
        pName              "Microsoft Office"
        dwUserData         0x00a4ff34
        state              0x00030048
        state2             0x80000300
        ExStyle            0x00010109
        style              0x84c000c4
        fnid               0x000002a4
        hImc               0x00000000
        bFullScreen        0y0
        hModule            0x314c0000
        pStackTrace        0x00000000
        pActCtx            0x00000000
        BFBITMAP                bc6809f8:80
        BFICON                  bc6809f8:40
        CBFAUTOHSCROLL          bc6809f8:40
        CBFOEMCONVERT           bc6809f8:80
        DF3DLOOK                bc6809f8:04
        EFAUTOHSCROLL           bc6809f8:80
        EFAUTOVSCROLL           bc6809f8:40
        EFMULTILINE             bc6809f8:04
        SBFSIZEBOXBOTTOMRIGHT   bc6809f8:04
        SFNOPREFIX              bc6809f8:80
        SFREALSIZECONTROL       bc6809f8:40
        WEFCONTROLPARENT        bc6809f6:01
        WEFDLGMODALFRAME        bc6809f4:01
        WEFTOPMOST              bc6809f4:08
        WEFWINDOWEDGE           bc6809f5:01
        WFBORDER                bc6809fa:80
        WFCAPTION               bc6809fa:c0
        WFCLIPSIBLINGS          bc6809fb:04
        WFCPRESENT              bc6809ec:08
        WFDIALOGWINDOW          bc6809ee:01
        WFDLGFRAME              bc6809fa:40
        WFFRAMEON               bc6809ec:40
        WFICONICPOPUP           bc6809fb:c0
        WFPOPUP                 bc6809fb:80
        WFTITLESET              bc6809ee:02
        WFTOPLEVEL              bc6809fa:40
        WFWIN31COMPAT           bc6809f1:01
        WFWIN40COMPAT           bc6809f1:02

ウィンドウクラスの表示

!dcls

ウィンドウステーション・デスクトップ・モニタ・ディスプレイ

ウィンドウステーション(userkdx)

!dws

デスクトップ(userkdx)

!ddesk

マルチモニタ(userkdx)

!dmon

DISPLAYINFO(userkdx)

!dy

SERVERINFO

!dsi。 -m オプションによる SYSTEMMETRICS は、XPではThemeによりオーバーライドされている可能性あり、要注意。

入力・ウィンドウメッセージ

メッセージキュー(userkdx)

!dq, !dmq

SendMessageの表示(userkdx)

!dsms

ホットキー(userkdx)

!dhot

キーボードステート(usrekdx)

キーボードレイアウト(userkdx)

デッドキーテーブル(userkdx)

フック(userkdx)

デバイスステート(userkdx)

HIDデバイス情報(userkdx)

System

ハンドルの評価

ATOMの表示(userkdx)

デバッグプリントの制御(chk版のみ)

リンクトリストの表示

CPU食いスレッドの表示(userkdx)

HardErrorダイアログ

IME関連

インプットコンテクスト

デフォルトIMEウィンドウ

!dti で表示される

-->

デバッグ事例

SendMessageで刺さっているとき(kd)

ささっているウィンドウメッセージが分かっていれば、!dsms -m msg で調べます。 それらしいものが見つかれば、Recv の et で示される値を !thread に渡してやり、スタックトレースを調べます。
必要に応じて ntsd をターゲットプロセスにアタッチし、ユーザモードで調べることも必要かもしれません。 例えば、ターゲットスレッドがクリティカルセクションを待っていれば、!lock や !critsec でオーナースレッドを調べ、さらに追跡します。

Sender が分かっていれば、t あるいは Win32Thread で示される値を !dsms -s に渡してやると、滞留している SendMessage の一覧が取得できます。 それらしいものを見つけて、あとは以下同文。

ありがちな例としては、

  1. ThreadA が、CritSec1 を所有。ThreadB に SendMessage。
  2. ThreadB のハンドラは、CritSec1 を所有しようとするが、ThreadA が所有しているためブロックされる。
  3. →デッドロック。

システムの負荷が妙に高いとき(kd)

AV: ウィンドウを閉じないまま、DLLがアンロードされたとき

フックの状態を確かめる(kd)

!ddesk でデスクトップの一覧を表示するとき、グローバルフックも同時に表示してくれます。 スレッド毎のフックは、!dhk -a。 個別のスレッドを見たい場合は、!dhk に、!process 0 4 で表示される Win32Thread の値を渡します。

関連リンク

Since 1996

一つ上へ

ホーム  ざれごと  ワシントン州  ツール  NT豆知識  Win32プログラミングノート  私的用語  ジョーク  いろいろ  ゲーム雑記  Favorites  掲示板   Mail