WADA-DEV(7) $ /ja/blog/gdb-fd-reconnect-trick/

NAME

gdb-fd-reconnect-trick

SYNOPSIS

gdb をデバッガとしてではなく「実行中プロセスのファイルディスクリプタを書き換える道具」として使う小技。標準出力を途中からログファイルに切り替えたり、pts をつなぎ替えたりできる。しょうもないけど知っていると効く話。

DESCRIPTION

はじめに

すでに動いているプロセスの出力先を、止めずに別の場所へつなぎ替えたいことがあります。gdb でプロセスにアタッチし、closeopen のシステムコールを直接呼ぶだけで実現できる。デバッガとしてではなく「実行中プロセスのファイルディスクリプタを書き換える道具」としての使い方です。

長時間動かしっぱなしのプロセスがあって、いまさらログをファイルに残しておきたくなる、という場面がある。あるいは別のターミナルから出力を覗きたい。普通なら起動し直して > log.txt を付けるところだが、止めたくない・止められないこともあります。

ファイルディスクリプタはプロセスごとに管理されていて、閉じると番号が再利用される。この性質を使えば、標準出力(fd 1)や標準エラー出力(fd 2)を閉じてから別のファイルを開き直すことで、出力先をつなぎ替えられます。

やり方

まず対象プロセスに gdb でアタッチする。

sudo gdb -p PID

あとは close で fd を閉じ、open で開き直すだけ。fd は最小の空き番号に割り当てられるので、1 を閉じてすぐ open すれば返り値は 1 になります。

(gdb) p close(1) # 標準出力を閉じる
$1 = 0 # 成功。プロセスは標準出力に書き込めなくなる
(gdb) p open("/dev/pts/5", 1) # pts5 を書き込み専用で開く
$2 = 1 # 成功。ファイルディスクリプタ 1 が返される
(gdb) p close(2) # 標準エラー出力を閉じる
$3 = 0 # 成功
(gdb) p open("/dev/pts/5", 1) # pts5 を書き込み専用で開く
$4 = 2 # ファイルディスクリプタ 2 が返される
(gdb) detach

開く先を /dev/pts/5 の代わりに "/tmp/app.log" のようなパスにすれば、標準出力を途中からログファイルへ切り替えられる。open の第 2 引数は flags で、ここでは書き込み専用(O_WRONLY = 1)を渡しています。

注意点

  • close した直後に open しないと、空いた fd 番号が他に取られて番号がずれることがある。1 を狙うなら 1 を閉じてすぐ開く。
  • gdb でアタッチしている間、対象プロセスは止まる。detach で必ず再開させる。
  • 既存の出力をすでに開いていた相手(パイプの先など)には、つなぎ替え後の出力は届かなくなる。
  • 子プロセスや複数の pts まで一括でつなぎ替えたい場合は、再帰的にたどる必要がある(参考リンク参照)。

まとめ

  • gdb は「実行中プロセスの fd を書き換える道具」としても使える
  • close(fd)open(path, flags) で出力先をつなぎ替えられる
  • 止めたくないプロセスのログを後から拾う、出力先を別端末に振る、といった小回りが効く

参考リンク

TAGS

gdb · デバッグ · Linux