gdb hacks - gdb とターゲットプロセスとの通信を観察する

| | コメント(0) | トラックバック(0)

gdb hacks 第 2 回。今回は以下のトピックを扱います。

  • gdb が関数の呼び出しでターゲットプロセス中に作成するデータ構造
  • gdb とターゲットプロセスとの通信を観察する方法

サンプルデバッグセッションのログを gdb_target_debug.txt に用意していますので参照してください。実験環境は例によって i386 の Debian GNU/Linux (sid) です。

gdb とターゲットプロセスとのやりとりは、以下のように 3 つほどデバッグスイッチを有効にすると観察することができます (infrun デバッグスイッチは gdb 6.4 以降から使えます)。

   119	(gdb) set debug target 1
   120	(gdb) set debug infrun 1
   121	(gdb) set debug lin-lwp 1
   122	(gdb) p chdir("/etc")

こうして chdir("/etc") を呼び出すと大量のデバッグメッセージが出力されます。順番に見ていきましょう。

   123	child:target_xfer_partial (2, (null), 0x0,  0xbfd13bf0,  0xbf9decb4, 4) = 4, bytes = 05 00 00 00
   124	child:target_xfer_partial (2, (null), 0x0,  0xbfd13bf0,  0xbf9decb0, 4) = 4, bytes = e0 8b 04 08
   125	infrun: proceed (addr=0xb7eee850, signal=0, step=0)
   126	child:target_xfer_partial (2, (null), 0x837a070,  0x0,  0x8048be0, 1) = 1, bytes = 31
   127	child:target_xfer_partial (2, (null), 0x0,  0xbfd13b50,  0x8048be0, 1) = 1, bytes = cc
   128	infrun: resume (step=0, signal=0)
   129	infrun: wait_for_inferior
   130	CW:  waitpid 4183 received 未知のシグナル(0) (terminated)
   131	CW:  waitpid 4179 received トレース/ブレイクポイント トラップ (stopped)
   132	infrun: infwait_normal_state
   133	infrun: TARGET_WAITKIND_STOPPED
   134	infrun: stop_pc = 0x8048be0
   135	infrun: BPSTATE_WHAT_STOP_SILENT
   136	infrun: stop_stepping
   137	child:target_xfer_partial (2, (null), 0x0,  0xbfd13b40,  0x8048be0, 1) = 1, bytes = 31

それぞれの意味は gdb のソースコードを grep して調べてほしいのですが、おおざっぱに説明するとだいたいこんな処理をしています。

   123	0xbf9decb4 のワードに 0x00000005 を書く (スタックに引数をプッシュ)
   124	0xbf9decb0 のワードに 0x08048be0 を書く (スタックに戻りアドレスをプッシュ)
   126	0x08048be0 のバイトを保存 (= 0x31)
   127	0x08048be0 のバイトに 0xcc (INT3) を書く (ブレークポイントの設定)
   128	ターゲットプロセス実行開始 (0xb7eee850 番地から)
   131	ブレークポイントを踏んだ
   137	0x08048be0 のバイトを復元

謎なアドレスがいくつかでてきました。

  • 0xbf9decb0 0xbf9decb4 はターゲットプロセスのスタック領域です (info registers で esp を見る)
  • 0x08048be0 は .text セクションのスタートアドレスです (info target で調べられます)
  • 0xb7eee850 は malloc() 関数のエントリポイントです (info symbol 0xb7eee850 で調べられます)

これはつまり gdb がターゲットプロセスに malloc(5) という関数呼び出しをさせていることを示します。関数からの戻りアドレスは .text 先頭の 0x08048be0 に設定され、ここに設置されたブレークポイントを踏んで gdb に制御が戻るという寸法です。

この直後に malloc() で確保した領域を 1 バイトづつ埋めにかかります。これは 0 終端文字列で "/etc" と書いていることは明白でしょう。

   138	child:target_xfer_partial (2, (null), 0x0,  0xbfd13da0,  0x804ec80, 1) = 1, bytes = 2f
   139	child:target_xfer_partial (2, (null), 0x0,  0xbfd13da0,  0x804ec81, 1) = 1, bytes = 65
   140	child:target_xfer_partial (2, (null), 0x0,  0xbfd13da0,  0x804ec82, 1) = 1, bytes = 74
   141	child:target_xfer_partial (2, (null), 0x0,  0xbfd13da0,  0x804ec83, 1) = 1, bytes = 63
   142	child:target_xfer_partial (2, (null), 0x0,  0xbfd13da0,  0x804ec84, 1) = 1, bytes = 00

こうして初期化したメモリ領域を使って、同様に chdir("/etc") の呼出しを実行します。

   143	child:target_xfer_partial (2, (null), 0x0,  0xbfd13fa0,  0xbf9decb4, 4) = 4, bytes = 80 ec 04 08
   144	child:target_xfer_partial (2, (null), 0x0,  0xbfd13fa0,  0xbf9decb0, 4) = 4, bytes = e0 8b 04 08
   145	infrun: proceed (addr=0xb7f47f10, signal=0, step=0)
   146	child:target_xfer_partial (2, (null), 0x834bed8,  0x0,  0x8048be0, 1) = 1, bytes = 31
   147	child:target_xfer_partial (2, (null), 0x0,  0xbfd13f00,  0x8048be0, 1) = 1, bytes = cc
   148	infrun: resume (step=0, signal=0)
   149	infrun: wait_for_inferior
   150	CW:  waitpid 4179 received トレース/ブレイクポイント トラップ (stopped)
   151	infrun: infwait_normal_state
   152	infrun: TARGET_WAITKIND_STOPPED
   153	infrun: stop_pc = 0x8048be0
   154	infrun: BPSTATE_WHAT_STOP_SILENT
   155	infrun: stop_stepping
   156	child:target_xfer_partial (2, (null), 0x0,  0xbfd13ef0,  0x8048be0, 1) = 1, bytes = 31
   157	$1 = 0

これで終了です。戻り値 0 を報告して、gdb のプロンプトに戻ってきました。

ここで gdb が重大なことを忘れていることに気づいたでしょうか。そう、malloc() で確保したメモリを free() していません。遊び終わったらゴミはきちんと片付けておきましょう (もっとも通常はそのアドレスを知る術はないのですが…)。

   162	(gdb) p free(0x804ec80)

このように gdb は式の評価中に文字列が出てくるとターゲットプロセス中で malloc() を呼び出してそのための領域を作ろうとします。つまり gdb が malloc() を見つけられないようなプログラム (static link して strip したバイナリなど) では、この機能は使えません。また malloc() で確保したメモリ領域は確保しっぱなしで開放されないので、デバッグ目的以外で、ターゲットプロセスの関数呼び出しはやめたほうがよさそうです。

今回は gdb のデバッグオプションをとりあげ、どのようにターゲットプロセスをいじっているのか確認する方法を説明しました。このデバッグ機能は新たなデバッグターゲットや、リモートデバッグのための gdb stub を作成する際に大変役に立ちます。

つづく。

カテゴリ

トラックバック(0)

このブログ記事を参照しているブログ一覧: gdb hacks - gdb とターゲットプロセスとの通信を観察する

このブログ記事に対するトラックバックURL: http://www.keshi.org/mt/mt-tb.cgi/32

コメントする

このブログ記事について

このページは、yaegashiが2006年2月28日 09:37に書いたブログ記事です。

ひとつ前のブログ記事は「gdb hacks - ターゲットプロセス内の関数を呼び出す」です。

次のブログ記事は「gdb hacks - gdb を電卓の代わりに使う」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 4.0