はじめに
すでに動いているプロセスの出力先を、止めずに別の場所へつなぎ替えたいことがあります。gdb でプロセスにアタッチし、close と open のシステムコールを直接呼ぶだけで実現できる。デバッガとしてではなく「実行中プロセスのファイルディスクリプタを書き換える道具」としての使い方です。
長時間動かしっぱなしのプロセスがあって、いまさらログをファイルに残しておきたくなる、という場面がある。あるいは別のターミナルから出力を覗きたい。普通なら起動し直して > 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)で出力先をつなぎ替えられる- 止めたくないプロセスのログを後から拾う、出力先を別端末に振る、といった小回りが効く