☆デバッグ
テストとデバッグは全く目的が違うプロセス
※良いテストとは単位時間あたり、多くのバグを発見するもの
※良いデバッグというのは単位時間あたり多くのバグを修正するもの
※バグは、正式なテストのプロセスからだけでなく、さまざまな方法で見つかる
◎デバッグのプロセスと準備
◆デバッグのプロセス
①バグの再現
②デバッグ
③動作の確認(テスト)
④期待する動作であれば終了、そうでなければ②へ
◆典型的なバグの切り分け方法
_◇プログラムの異常終了
│カーネルか否か
│ カーネルパニック⇒カーネルの可能性大
│ ダンプ採取
│ 原因の分類
│ コール
│ WDT
│ メモリ不正アクセス
│ ダンプとれない
│ カーネルメッセージ解析
│ アプリの可能性大
│ SIGSEGVか⇒メモリの不正アクセス
│ その他の原因⇒GDBなど
_◇プログラムが終了しない
│カーネルが怪しい
│ WDT設定
│ WDTで検出できない
│ SysRqキー、minicom
│ 負荷が高い
│ デットロック
│ 無限ループ
_◇その他の現象
①デグレデーション
②メモリリーク
_◇Linuxカーネルが怪しい場合
①ps
・ 表示が途中で止まる
・ ステータスがD
②ping
・ 応答が返らない
③キーボード
・ キー入力できない
④kill -9
・ 終了させられない
⑤strace
・ アタッチできない
⑥gdb
・ アタッチできない
⑦カーネルメッセージ
・ softlockupなどのメッセージ
◆非テストでバグ検出した場合
_◇再現させる前の準備
①環境構築
⇒出来る限り問題発生環境にそろえる
⇒細かい設定やパラメータの差が問題となることが多い
⇒あせって設定などでミスをしやすいので注意
②ヒアリング
⇒第三者が検出したバグを再現させる場合、懸念事項をまとめてヒアリングする。相手がバグとは関係ないと思って言わないことに問題があることもある
_◇再現させた後の確認
①本当に同じ現象か
②再現率など
_◇解析時の注意
①現象を目で確認。キーは操作できるか、pingがとおるのか、発生までの時間など。
②できるだけ範囲を狭める
③事実を元に判断する
_◇原因不明時の対処
①時にはハードウエアも疑う
②過去の事例
③デバッグ情報が出力されるような仕掛け
☆GDB
The GNU Project Debugger
gccが扱える言語ならすべてデバッグできる
http://www.gnu.org/software/gdb/gdb.html
◆デバッグ対象プログラムのコンパイル
例)
$ gcc -g test01.c -o test01
│ -g
│ OS本来の形式(stabs, COFF, XCOFF, DWARF)でデバッグ情報を生成する。
│ -gcoffなどと各形式を指定することもできる
│ -ggdb
│ GDB用にデバッグ情報生成
│ -gstags+
│ GDB専用のGNU拡張stabs形式で情報生成
│ -O0
│ 英字Oと数字0、最適化の意図的解除
⇒最適化オプションをつけるとデバッガが上手く働かない可能性がある。
例)
$ gcc -Wall -g -o spawnGDB spawn.c
◆GDBの起動
①デバッグ対象のプログラムを指定して
例)
$ gdb ./test01
例)
$ gdb -c core ./a.out
coreは異常終了でダンプされるコアファイルの解析のとき
②後からアタッチする
例)
$ gdb
_◇起動シーケンス
①ホームディレクトリに.gdbinitがあればそれを読み込む
②コマンドラインオプションとオペランドを処理
③実行ディレクトリ上に.gdbinitがあればそれを読み込む
_◇起動オプション
-nx
・ 初期化ファイルを読み込まない
-quiet
・ 初期メッセージを表示しない
-batch
・ バッチモード
-nowindows, -nw
・ CUIモード
-cd directory
・ 指定ディレクトリを作業ディレクトリとして指定
◆GDBの終了
・ q
・ quit
・ [Ctrl] + [d]キー
※GDBを終了する前に continue などでデバッグ対象のプロセスを最後まで実行して終了させる
◆コマンド
TABキーで補完される
_◇help
・ 簡単なコマンドクラス名一覧を表示する。
・ help dataでdata関連コマンド表示
_◇run, start
ユーザプログラムを実行。引数を与えるとユーザプログラムに与えられる。
例)
(gdb) run -n 5
⇒プログラムの引数として -n 5 を与えて実行
⇒一度引数付runコマンドを実行すると、指定された引数はargsという変数に記憶される
⇒引数無runコマンドはargsを参照するので、同一セッション内では引数は省略できる。
show args
⇒args表示
set args コマンドライン引数
⇒argsの設定
set args
⇒argsクリア
※start
main()にブレークポイントを設定, main()まで実行
_◇環境変数の変更、実行環境など
※set environment varname [=] value
※unset environment vaname
※show environment [varname]
※set args
ユーザプログラムに与える引数を指定できる。
※show args
引数を表示
※path directory
環境変数PATHの先頭にディレクトリ追加。ディレクトリ間は「:」かスペース
※path $cwd
作業ディレクトリを参照
※show paths
環境変数PATHの表示
※cd
作業ディレクトリの変更
※pwd
作業ディレクトリを表示
_◇プロセス、プログラム情報
※info proc
プロセスに関しての情報を表示
⇒cygwin上ではサポートされない
※info proc mappings
プログラムがアクセス可能なアドレス範囲を表示する。
⇒cygwin上ではサポートされない
⇒linux上では、ライブラリ、プログラム、スタックなどのアドレスマップ情報を示す。
※info proc all
すべての情報を表示
⇒cygwin上ではサポートされない
※info proc times
軌道時間、消費時間など
⇒cygwin上ではサポートされない
※info proc id
プロセスID
⇒cygwin上ではサポートされない
※info proc status
プロセスの状態表示
⇒cygwin上ではサポートされない
※info program
ユーザプログラムの状態情報
プログラムがどんなフォルトで止まったのかなど分かる
※info files
⇒cygwin上では、実行ファイル上でのメモリマップが表示される
⇒linux上では実行ファイル上での各セグメントのメモリマップが表示される
※info target
⇒cygwin上では、実行ファイル上でのメモリマップが表示される
⇒linux上では実行ファイル上での各セグメントのメモリマップが表示される
※list 行番号
プログラムリストの表示
⇒行番号をつけなければ、現在実行中のあたりが表示される
_◇ブレークポイント
①break 関数名
例)
b main
⇒c++の場合は、マングルドシンボルで指定する必要がある
②break 移動値 (+-)
指定行数だけ先か手前
③break 行番号
※プログラムのソースが複数ある場合は、ソース名を先に指定する
例)
b compil.c:516
④break *アドレス
例)
b *0x08116fd6
⑤break
次の命令で止まる
⑥break 停止場所 if 条件
例)
b iseq_compile if node==0
⑦tbreak
1回だけ停止させる
⇒ブレーク後解除される
⑧hbreak
ハードウエアブレーク
ROM領域などで指定。CPUによっては利用できない
⇒一時ハードウエアブレーク
thbreak
⑨rbreak 正規表現式
正規表現にマッチするすべての関数でブレーク
⑩info breakpoints
設定ブレークポイント一覧
例)
info break
info b
ブレークポイント番号とともに表示される
⑪clear
次の命令に設定されているブレークポイントをクリア
clear [ソース名:]関数名
clear [ソース名:]行番号
⑫delete
すべてのブレークポイント削除
引数つけると指定箇所のみ
⇒info b で表示されるブレークポイント番号を指定
⑬disable
すべてのブレークポイント無効化
引数つけると指定箇所のみ
⑭enable
すべてのブレークポイント有効化
引数つけると指定箇所のみ
⑮condition 番号 式
指定された番号のブレークポイントの成立条件として式を設定。真のとき停止。式を指定しないと条件削除
⑯ignore 番号 count
指定された番号のブレークポイントが指定countに達するまで無視。
※関数に対するブレークポイントの設置
(gdb) b func
⇒関数funcにブレークポイントを設定するが、停止するのはソースコードに対応する少し後のアドレスとなる
(gdb) b *func
⇒関数funcにブレークポイントを設定する。関数先頭のスタック操作前でブレークする
_◇ウオッチポイント
①watch 式
式が真になったら停止
②rwatch 式
式の対象をリードアクセスで停止
③awatch 式
式の対象にアクセスで停止
例)
awatch short_output
変数 short_output にアクセスしたら停止
④info watchpoints
ウオッチポイント一覧
_◇ローカル変数の表示
info local
_◇関数引数の表示
info args
※c++の場合、クラスのメンバ関数においては、クラスインスタンスthisへのポインタも関数引数として表示される。
_◇display
displayコマンドによりプログラムが停止するたび所望の変数、レジスタなどを表示させることができる
例)
display $eip
※複数のdisplayコマンドを発行し、複数の情報を表示させることも可能
※display解除
undisplay
_◇ブレークポイントコマンド
ブレークポイントで停止したときに自動的に実行する
commands ブレークポイント番号
コマンド
…
end
_◇プログラム実行
①continue [ブレークポイントを無視する回数]
停止箇所から実行再開、指定回数だけブレークポイント無視
⇒デバッグ対象のプロセスを最後まで実行して終了させるのにも使える
②step [回数]
1行ステップ実行。回数指定すると回数分指定実行。
※アセンブリ言語の1命令単位での実行
stepi
③next [回数]
stepと類似するが、関数呼び出しの場合、関数を実行してから停止。
※アセンブリ言語の1命令単位での実行
nexti
④finish
現在の関数が終了するまで実行して停止。
⑤until
ループを抜けるまで実行して停止
⑥jump 行番号
行番号からの実行再開。
⑦call 関数式
関数を呼び出す
_◇バックトレース、フレーム
①backtrace
全スタックのバックトレース情報を表示する
⇒例外などが発生したときにトレースできる
⇒実行中の関数が呼び出しの逆に列挙される
※先頭に表示されているのが関数ごとの「フレーム」
例)
(gdb) bt
※bt N
最初のNフレームだけ表示
※bt -N
最後のNフレームだけ表示
※bt full
ローカル変数も表示
②frame
引数なし。。。現在のフレームに関して表示,
フレーム番号をつける⇒フレームを移動
⇒現在フレームを表示
⇒フレーム番号#nが表示されているのでフレームの位置を見失うことない
例)
(gdb) frame 2
フレームへ移動
⇒移動したフレームを表示
⇒この状態で、printコマンドなどで変数の状況を調べられる。
③info frame
選択されたフレームに関する詳細表示
例)
(gdb) info frame 2
⇒eip値、呼び出し元フレーム、言語、引数リストの位置、セーブされているレジスタなど表示
④up/down
フレームをたどってアップ、ダウンする。
_◇データ表示
同名の変数があるばあい、スコープに注意
①print 式
式の内容を表示
例)
(gdb) print optarg
⇒変数 optarg の値を表示する
例)
(gdb) p *argv
(gdb) p argv[0]
※レジスタ名に$をつければレジスタ表示も可能
(gdb) p $eax
⇒プログラムカウンタは$pcでも$eipでもよい
※p/フォーマット指定
フォーマット文字
x 16進
d 10進
u 符号なし10進
o 8進
t 2進
a アドレス
c 文字
f 浮動小数
s 文字列
※Cと同様なprintf
例)
(gdb) printf “%.2f\n”, *(float*)0xアドレス
②xアドレス
アドレスの内容をダンプ
※x/NFU ADDR
ADDR:アドレス
N:繰り返し回数
F:
U:表示幅
※フォーマット文字
⇒pのフォーマットに加えて
i 機械語命令
※表示幅
b バイト
h ハーフワード
w ワード
g ジャイアントワード
例)
x/40w $SP
⇒スタックポインタ位置から40ワードダンプ
例)
(gdb) x/i $pc
⇒プログラムカウンタの指す位置の命令
⇒フォルトが発生した場合などは、フォルト発生の命令を確認できる。
③info local
ローカル変数の表示
_◇ディスアセンブル
xコマンドによるディスアセンブルに加えて
disassemble
disassemble プログラムカウンタ
disassemble 開始アドレス 終了アドレス
例)
(gdb) disassem $pc $pc+50
例)
(gdb) disas main
(gdb) disas sum_till_MAX
_◇レジスタ表示
info registers
例)
(gdb) info reg
例)
(gdb) i r eip ebp
※全レジスタ表示
i all-r
⇒FPU, XMM, MM全てのレジスタが表示される
※フローティングポイントレジスタを含む情報表示
i float
⇒8本のFPUレジスタとステータス、コントロールワードなどが表示される
※FPUレジスタの個別表示
例)
info reg $st0
あるいは
print $st0
※xmm, mmレジスタについては、共用体としてアクセスされるので printコマンドを用い
例)
print $xmm.v4_float[0]
のようにアクセスする
⇒どのような構造体名で認識されるかは info all-rで見ること
_◇変数への代入
式の中に代入すればよい
_◇式
gdbはC/C++の演算子を使って式を組み立てることができる
ポインタの間接参照やアドレス参照も可能
例)
print *ptr
print &len
※「@」により連続したメモリ範囲を「人工配列」として扱うことができる。
例)arrayがメモリへのポインタ、lenが要素数を保持する変数
print *array@len
※構造体の間接参照も->で可能
※配列[]も可能
※スコープ演算子 ::
構造体、共用体、クラスについてスコープを制御できる
※Cに準じた定数を使用できる。
_◇コンビニエンス変数、ヒストリ
値の保持、参照のためコンビニエンス変数を使用できる
※コンビニエンス変数は”$”で始まる
※show convenience
コンビニエンス変数の一覧
※ヒストリ
printコマンドで表示した値はヒストリとして記録される
値を参照することができる
show value
⇒ヒストリ内の最後の10個を表示
$ 値ヒストリの最後の値
$n 値ヒストリのn番目の値
$$ 値ヒストリの最後から1つ前
$$n 値ヒストリの最後からn番目
$_ xコマンドで最後に調査したアドレス
$__ xコマンドで最後に調査したアドレスの値
$_exitcode デバッグしているプログラムの終了コード
$bpnum 最後に設定したブレークポイント番号
_◇コマンドヒストリ
show history
⇒ヒストリの表示
_◇コマンド定義
define コマンド名
コマンド
end
※定義コマンドの説明
document コマンド
コマンド
end
⇒helpコマンドで表示できる
_◇コアファイルの生成
(gdb) generate-core-file
_◇C/C++で使用できる演算子
,
=
?:
||
&&
|
^
&
==
|=
< >
<= >=
<< >>
@
・ GDBの人工配列演算子。連続したメモリ範囲を配列として扱うことができる
・ 例)
・ arrayがポインタであってメモリを指している
・ lenにarrayの型での要素数が入っている
・
・ print *array@len
・ ⇒配列としての表示を得る
+
–
*
/
%
++
—
*
&
–
!
~
. もしくは->
.*もしくは->*
[]
()
::
・ C++のスコープ解決演算子
・ GDBのスコープ解決演算子
◆演算子
_◇@
人工配列演算子
例)
print *array@len
ポインタ*arrayの指すメモリの内容 len個分を配列として表示
_◇c/c++
c/c++をデバッグしている場合には、c/c++の演算子を使用することができる。
構造体などのメンバ参照、スコープ解決などもそれら演算子による
◆アタッチ
実行中のプロセスをデバッグする。
_◇attach プロセスID
プロセスIDにアタッチする
例)
$ ps aux|grep プログラム名
⇒pidを確認
(gdb) attach nnn
_◇detach プロセスID
アタッチ中のプロセスを開放する
_◇kill
GDBが管理しているプロセスを強制終了する
実行例)
#include
int main()
{
long long cnt = 0;
for(;;)
{
cnt++;
}
}
$ gcc -g test02.c -o test02
$ ./test02 &
プロセスIDが表示される
$ gdb
(gdb) attach プロセスID
◆コアファイルによるデバッグ
$ gdb -c corefile ./a.out
◆設定ファイル、環境変数
_◇コマンドヒストリファイル
./.gdb_history
環境変数GDBHISTFILEにより変更可能
_◇初期化ファイル
初期化実行順
①$HOME/.gdbinit
②コマンドライン
③./.gdbinit
④-xオプションで与えられるコマンドファイル
☆OllyDbg
◆Versionとインストール
_◇1.10
_◇プラグイン
OllyDump
OllyScript
Labeler
◆起動
_◇TIPS
管理者として実行
エクスプローラの右クリックで登録
オプション>エクスプローラメニューに追加
_◇デバッギーの起動、アタッチ、デタッチ
※デバッガからのデバッギー起動
ファイル>開く
⇒初期状態のままであれば、エントリーポイントで停止
◆オプション
_◇解析詳細設定>イベント
システムブレークポイント>エントリーポイント前のプロセス実行準備段階で停止
◆操作
_◇メニュー:ファイル
開く
・ 実行ファイルを選択し実行
アタッチ
・ 実行されているプロセスを選択してアタッチ
終了
・ OllyDbg 終了、デバッギーも強制終了
_◇メニュー:表示
ログ
・ DLLの読みこみ、スレッド作成、デバッグ出力、ブレークなどの履歴
実行モジュール
・ デバッギーのプロセスメモリ上の実行可能モジュール一覧
メモリ
・ デバッギーのプロセス内のメモリマップ
スレッド
・ デバッギーのプロセス内のスレッド一覧
ウインドウ
・ デバッギーが表示しているウインドウの一覧
ハンドル
・ デバッギーが開いているハンドル一覧。ただし、デバッギーがシステム関連プロセスであると、APIによってはハンドル取得でフリーズする可能性がある。
CPU
・ CPUウインドウ表示
ブレークポイント
・ INT3ブレークポイントの一覧
ウオッチ
・ ウオッチウインドウ表示
参照
・ 各種検索結果など表示
ラントレース
・ ラントレースウインドウをひらく
※ラントレース
│ ステップ実行しながら実行されたコードの情報を記録する
│ カーネルモード部分はステップ実行がうまくいかない
│ カーネルモードへの切り替えは以下の命令による
│ Windows 2000 INT2E
│ WindowsXP(32) SYSENTER
│ WindowsXP(64) SYSCALL
│ WindowsVista(x86) SYSENTER
ファイル
・ 簡易バイナリエディタ
テキストファイル
・ 簡易テキストエディタ
_◇メニュー:解析
実行
・ デバッギーの実行再開
一時停止
・ デバッギー一時停止
再スタート
・ デバッギー、強制終了&再起動
解析終了
・ デバッギー強制終了
詳細ステップ実行
・ ステップイン
ステップ実行
・ ステップオーバー
詳細自動ステップ実行
自動ステップ実行
リターンまで実行
・ リターンまで自動ステップ実行
ユーザーコードまで
・ ユーザーコードまで自動ステップ実行
ハードウエアブレークポイント
・ ハードウエアブレークポイント設定箇所の一覧
_◇メニュー:オプション
環境設定
解析詳細設定
ジャスト・イン・タイムデバッグ
_◇条件式の記述方法
①16進数はそのまま、符号付十進数は100.のようにピリオドつける
②レジスタ名をそのまま書くと符号なしとみなし、ピリオドつけると符号付といなす
③メモリ参照は[アドレス]。DWORD PTRなどでサイズを指定
④四則演算は+-*/
⑤条件演算子 == != > >= < <=
⑥WM_COMMMANDの定数をMSGで、あるいはWM_ACTIVATEなどのメッセージ名を使用可能
⑦文字列比較
・ [STRING 40xxxx]==”Good”
・ EAX==”Good”
_◇命令検索
具体的なレジスタ名などに変えて以下のあいまい名を指定可能
・ R8 すべての8ビットレジスタ
・ R16 すべての16ビットレジスタ
・ R32 すべての32ビットレジスタ
・ CONST すべての定数
・ JCC すべての条件ジャンプ
シーケンス検索では、検索対象外のあらゆる命令をANY 2などとして表現できる
_◇CPUウインドウ
①逆アセンブラペイン
逆アセンブルコードを表示する
※各カラムをダブルクリックすることで
・ アドレス表示の相対、絶対切り替え
・ ブレークポイント(INT3)の設定、解除
・ 行アセンブル
・ コメント追加
を行うことができる
※右クリックメニューもしくはショートカットキーから各種の処理を実行できる
BKUP
・ メモリブロックのバックアップを作成する
・ バックアップと現在データとの比較表示がされる
・ バックアップのファイル入出力も行うことができる
コピー
・ カラムイメージをクリップボードもしくはテキストファイルにコピーする
バイナリエ
・ バイナリで編集
逆アセンブル修正
・ 1行アセンブラで修正。あまりをNOPで埋めるオプションあり
・ HEXは0ABのように記す。API関数は
・ kernel32.ExitProcess
・ のように入力
※注意点
・ EBP,ESI,EDIの内容を修正コードの前後で保持すること
・ ⇒PUSHAD、POPAD、PUSHFD、POPFDで必要に応じてレジスタ退避
※誤った逆アセンブルコードが表示される場合はコントロールAで再分析
※RETの直後の逆アセンブルに失敗するときは、一度RETをNOPに変えて逆アセンブルし、後からRETに戻す
ラベル
・ 追加、編集が可能
ブレークポイント
・ F2キーでも設定できる
・ 条件つきブレークも設定できる。
・ ログつきブレークによりブレーク時に関数引数などをログ出力できる。
・ メモリアクセスブレークは同時に一つだけ
・ ⇒トリッキーなので危険
・ ハードウエアブレークポイントの設定も可能
ヒットトレース
・ 監視指定内の実行されたコードに印をつける
ラントレース
・ ヒットトレースに加えて実行履歴を記録する
・ ヒットトレースの範囲指定と連動して解除される
移動
・ 制御転送命令の場合、飛び先に移るか否かを指示できる。
内容表示
・ ダンプ
検索
・ ラベル名:インポート、エクスポート関数に加えユーザー独自ラベルも一覧表示
・ さらに右クリックから詳細動作を指定
・ コマンド、コマンドシーケンス、定数、文字列なども検索可能
参照を検索
・ 逆アセンブルコードを参照しているコードの一覧を表示する
・ 同じ定数を参照しているものも検索可能
実行ファイルへコピー
・ 修正内容を反映させる。別名での保存となる。
②インフォメーションペイン
逆アセンブルペイン上で選択されているコードについての情報を表示する
③ダンプペイン
プロセスメモリエディタ(編集はバイナリエディタダイアログから)
④レジスタペイン
レジスタ内容を表示する。表示はドラッグで移動可能
ダブルクリックで修正用ウインドウがひらく
⑤スタックペイン
スタック情報を表示する。現在のスタックトップは黒字に白地。多くのAPI関数で、スタックに積まれている情報が表示される。変更も可能だが、データはそのままではクリアされない。
_◇ログウインドウ
処理のログが表示される。ブレーク状況、ログ付ブレークポイントの出力、OutputDebugString関数の出力もされる。
_◇実行可能モジュールウインドウ
プロセスメモリ上のモジュール一覧。
_◇メモリマップウインドウ
_◇スレッドウインドウ
_◇ウインドウ一覧ウインドウ
_◇ハンドルウインドウ
_◇パッチウインドウ
_◇ブレークポイントウインドウ
_◇ウオッチウインドウ
☆debug(MS-DOS)、debug、debugx(FreeDOS)