ptrace(2) プロセスのトレース

書式

#include <sys/ptrace.h>


long ptrace(enum __ptrace_request request, pid_t pid,
void *addr, void *data);

説明

ptrace() システムコールは、親プロセスが、別のプロセスの実行の監視/制御を 行ったり、コアイメージ (core image) やレジスタの調査/変更を 行ったりする手段を提供する。 ptrace() は、主にブレークポイントによるデバッグやシステムコールのトレースを 実装するのに用いられる。

トレースを開始するには、まず親プロセスで fork(2) を呼び出す。生成された子プロセスで PTRACE_TRACEME を行い、続いて (典型的には) exec(3) を行なう。 別の方法としては、 親プロセスが既存のプロセスに対して PTRACE_ATTACH を使用し、トレースを開始する。

トレースの実行中、子プロセスはシグナルが配送されるたびに、 たとえそのシグナルが無視すべきものであっても停止する (SIGKILL は例外で、通常どおりの効果をもたらす)。 親プロセスには次の wait(2) で通知され、停止している間に子プロセスを調べたり修正したりすることができる。 そして親プロセスは子プロセスの実行を再開させるが、配送された シグナルを無視することもできる (あるいは代わりに別のシグナルを 配送することもできる) 。

親プロセスがトレースを終了する際には、 PTRACE_KILL を使用して子プロセスを終了させることもできるし、 PTRACE_DETACH を用いて通常のトレースなしのモードにして、 実行を継続させることもできる。

request の値がこのシステムコールの動作を決定する:

PTRACE_TRACEME
このプロセスが親プロセスによってトレースされることを表す。 このプロセスに (SIGKILL 以外の) シグナルが配送されると、 プロセスは停止し、親プロセスに wait(2) を通じて通知される。 また、これ以降はこのプロセスが execve(2) を呼び出す度に SIGTRAP が送信されるようになる。 これによって、親プロセスは 新しいプログラムが実行を開始する前に制御することができる。 親プロセスが自プロセスをトレースするつもりがない場合には、 おそらくこのプロセスは本要求を行うべきではないだろう。 (pid, addr, data は無視される。)

上記の要求は子プロセスだけが行なうものである。 残りは親プロセスだけが行なうものである。 以下の要求では、pid で操作の対象となる 子プロセスを指定する。 PTRACE_KILL を除き、要求を行なうためには 子プロセスは停止していなければならない。

PTRACE_PEEKTEXT, PTRACE_PEEKDATA
子プロセスのメモリの addr の位置から 1 ワードを読み出す。読み出したワードは ptrace() の返り値として返される。 Linux ではテキスト (text) とデータ (data) で 同じアドレス空間を使用するため、この 2 つの要求は現在のところ 同じものである。 (引き数 data は無視される。)
PTRACE_PEEKUSER
子プロセスの USER 領域のオフセット addr の位置から 1 ワードを読み込む。USER 領域にはそのプロセスの レジスタ (registers) などの情報が保持されている (<sys/user.h> を参照)。読み込んだワードは ptrace() コールの結果として返される。 たいていはオフセットはワード境界になければならないが、 アーキテクチャによってはその必要はない。 「注意」の節を参照。 (data は無視される。 )
PTRACE_POKETEXT, PTRACE_POKEDATA
ワード data を子プロセスのメモリの addr の位置へコピーする。上と同様に、現在のところ二つの 要求は同じものである。
PTRACE_POKEUSER
ワード data を子プロセスの USER 領域のオフセット addr の位置にコピーする。 上と同様に、通常、オフセットはワード境界になければならない。 カーネルの完全性 (integrity) を維持するため、 変更内容によっては USER 領域の変更は禁止されている。
PTRACE_GETREGS, PTRACE_GETFPREGS
それぞれ、子プロセスの汎用レジスタ、浮動小数点レジスタを親プロセスの data の位置にコピーする。この data の書式に関しては <sys/user.h> を参照すること。(addr は無視される。)
PTRACE_GETSIGINFO (Linux 2.3.99-pre6 以降)
停止の原因となったシグナルに関する情報を取得する。 siginfo_t 構造体 (sigaction(2) 参照) を子プロセスから親プロセスの data の位置にコピーする。 (addr は無視される。)
PTRACE_SETREGS, PTRACE_SETFPREGS
それぞれ、子プロセスの汎用レジスタ、浮動小数点レジスタに 親プロセスの date の位置からコピーする。 PTRACE_POKEUSER と同様に、汎用レジスタによっては 変更が禁止されている場合がある。 (addr は無視される。)
PTRACE_SETSIGINFO (Linux 2.3.99-pre6 以降)
シグナル情報を設定する。 siginfo_t 構造体を親プロセスのデータ data の位置から 子プロセスにコピーする。 この処理を行うことができるのは、子プロセスに通常は配送されるはずで トレーサに捕捉されたシグナルについてだけである。 これらの通常のシグナルと ptrace() 自身が発生するシグナルを見分けるのは難しいかもしれない。 (addr は無視される。)
PTRACE_SETOPTIONS (Linux 2.4.6 以降; バグの章にある警告も参照)
親プロセスの data に基づいて ptrace のオプションを設定する (addr は無視される)。 data はオプションのビットマスクとして解釈され、 オプションには以下のフラグを指定できる:
PTRACE_O_TRACESYSGOOD (Linux 2.4.6 以降)
システムコールのトラップが配送されたときに、シグナル番号のビット 7 を設定する (すなわち、(SIGTRAP | 0x80) を配送する)。 これにより、トレーサが通常のトラップとシステムコールによるトラップを 区別しやすくなる。 (PTRACE_O_TRACESYSGOOD はどのアーキテクチャでも動作しない可能性がある。)
PTRACE_O_TRACEFORK (Linux 2.5.46 以降)
次の fork(2) 呼び出し時に SIGTRAP | PTRACE_EVENT_FORK << 8 で 子プロセスの動作を停止させ、 新たに fork されたプロセスのトレースを自動的に開始し、 SIGSTOP でそのプロセスの実行を開始する。 新しいプロセスの PID は PTRACE_GETEVENTMSG で取得できる。
PTRACE_O_TRACEVFORK (Linux 2.5.46 以降)
次の vfork(2) 呼び出し時に SIGTRAP | PTRACE_EVENT_VFORK << 8 で 子プロセスの動作を停止させ、 新たに vfork されたプロセスのトレースを自動的に開始し、 SIGSTOP でそのプロセスの実行を開始する。 新しいプロセスの PID は PTRACE_GETEVENTMSG で取得できる。
PTRACE_O_TRACECLONE (Linux 2.5.46 以降)
次の clone(2) 呼び出し時に SIGTRAP | PTRACE_EVENT_CLONE << 8 で 子プロセスの動作を停止させ、 新たに clone で作成されたプロセスのトレースを自動的に開始し、 SIGSTOP でプロセスの実行を開始する。 新しいプロセスの PID は PTRACE_GETEVENTMSG で取得できる。 このオプションで全ての clone(2) コールを捕まえられるわけではない。 子プロセスが CLONE_VFORK フラグ付きで clone(2) を呼び出した場合、 PTRACE_O_TRACEVFORK が設定されていれば代わりに PTRACE_EVENT_VFORK が配送される。 また、子プロセスが終了シグナルを SIGCHLD に設定して clone(2) を呼び出した場合は、 PTRACE_O_TRACEFORK が設定されていれば PTRACE_EVENT_FORK が配送される。
PTRACE_O_TRACEEXEC (Linux 2.5.46 以降)
次の execve(2) 呼び出し時に SIGTRAP | PTRACE_EVENT_EXEC << 8 で子プロセスの動作を停止させる。
PTRACE_O_TRACEVFORKDONE (Linux 2.5.60 以降)
次の vfork(2) 呼び出し時に SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8 で子プロセスの動作を停止させる。
PTRACE_O_TRACEEXIT (Linux 2.5.60 以降)
終了 (exit) 時に SIGTRAP | PTRACE_EVENT_EXIT << 8 で子プロセスの動作を停止させる。子プロセスの終了ステータスは PTRACE_GETEVENTMSG で取得できる。 この停止はレジスタがまだ参照可能であるプロセス終了処理の初期に行われ、 トレーサはどこで終了が発生したかを知ることができる。 通常の終了通知 (exit notification) はプロセスの終了処理が完了した後に 行われる。コンテキストを参照することはできるにも関わらず、 トレーサはこの時点から終了を止めることはできない。
PTRACE_GETEVENTMSG (Linux 2.5.46 以降)
発生したばかりの ptrace イベントに関するメッセージを (unsigned long 型で) 取得する。 取得したメッセージは親プロセスの data の位置に格納される。 得られる内容は、 PTRACE_EVENT_EXIT の場合は子プロセスの終了ステータスであり、 PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK, PTRACE_EVENT_CLONE の場合は新しいプロセスの PID である。 Linux 2.6.18 以降では、新しいプロセスの PID は PTRACE_EVENT_VFORK_DONE で入手できる。 (addr は無視される。)
PTRACE_CONT
停止した子プロセスの実行を再開させる。 data がゼロでなく、 SIGSTOP でもなければ、 子プロセスに配送されるシグナルと解釈される。 ゼロや SIGSTOP の場合はシグナルは配送されない。 これを使うと、例えば、親プロセスは 子プロセスに送られたシグナルを実際に配送するかどうかを 制御することができる。(addr は無視される。)
PTRACE_SYSCALL, PTRACE_SINGLESTEP
PTRACE_CONT と同様に停止した子プロセスを再開する。ただし、 PTRACE_SYSCALL の場合は子プロセスが 次にシステムコールに入るかシステムコールから抜けるかする時に、 PTRACE_SINGLESTEP の場合は 1 命令 (instruction) 実行した後に停止させる (通常どおり、子プロセスはシグナルを受け取った場合にも停止する)。 親プロセスから見ると、子プロセスは SIGTRAP を受信して停止したように見える。そのため、例えば PTRACE_SYSCALL を使うと、1回目の停止で引き数を調べて PTRACE_SYSCALL を実行し、 2回目の停止でシステムコールの返り値を調べる、 というようなことができる。 引き数 dataPTRACE_CONT の場合と同じ様に解釈される。 (addr は無視される。)
PTRACE_SYSEMU, PTRACE_SYSEMU_SINGLESTEP (Linux 2.6.14 以降)
PTRACE_SYSEMU は、実行を再開し、次のシステムコールに入る時に停止させる。 システムコールは実行されない。 PTRACE_SYSEMU_SINGLESTEP も同様だが、システムコールでない場合には 1 命令 (singlestep) だけ実行した時点でも停止させる。 このコールは User Mode Linux のように子プロセスのシステムコールを全て エミュレートしようとするプログラムで使用される。 引き数 dataPTRACE_CONT の場合と同じ様に解釈される。 (addr は無視される。 全てのアーキテクチャでサポートされているわけではない。)
PTRACE_KILL
子プロセスに SIGKILL を送り終了させる。(addrdata は無視される。)
PTRACE_ATTACH
pid で指定されたプロセスに接続 (attach) し、それを呼び出し元のプロセスの 子プロセスとしてトレースできるようにする。子プロセスは PTRACE_TRACEME したかのように振舞う。呼び出し元のプロセスはそのほとんどの目的において、 その子プロセスの実際の親になる (例えば、子プロセスのイベントの 通知を受けとったり、 ps(1) で親として表示されたりする)。しかし、子プロセスで getppid(2) を実行した場合には元の親プロセスの PID が返される。 子プロセスには SIGSTOP が送られるが、この呼び出しが完了するまでに 必ずしも停止するとは限らない。子プロセスの停止を待つには wait(2) を使用すること。(addrdata は無視される。)
PTRACE_DETACH
PTRACE_CONT と同様に停止した子プロセスを再開する。ただし まずそのプロセスからの分離 (detach) を行い、 PTRACE_ATTACH での親の切り換えによる効果と PTRACE_TRACEME の効果を取り消す。意図したものではないだろうが、 Linux では、トレースされている子プロセスはどのような方法でトレースを 開始されたとしても、この方法で分離 (detach) することができる。 (addr は無視される。)

返り値

成功すると、 PTRACE_PEEK* の場合は要求したデータを返し、 それ以外の場合は 0 を返す。 エラーの場合は -1 を返し、 errno が適切に設定される。 PTRACE_PEEK* が成功して返す値も -1 になることがあるため、 そのような要求の場合には、呼び出し元は errno を調べ、エラーか発生したのかどうかを判断しなければならない。

エラー

EBUSY
(i386 のみ) デバッグレジスタの確保または解放でエラーが発生した。
EFAULT
親プロセスまたは子プロセスのメモリの不正な領域に読み書きしようとした。 おそらくその領域がマッピングされていないか、 その領域へのアクセスが許されていないかである。 不運なことに、Linux ではこのようなエラーの場合、多かれ少なかれ 恣意的に EIO を返したり EFAULT を返したりすることがある。
EINVAL
不正なオプションを設定しようとした。
EIO
request が不正である。 または、親プロセスまたは子プロセスのメモリの 不正な領域に読み書きしようとした。 または、ワード境界違反があった。 または、実行再開の要求で不正なシグナルを指定した。
EPERM
指定したプロセスをトレースすることができない。これは親プロセスが 必要な権限 (必要なケーパビリティは CAP_SYS_PTRACE) を持っていないことが原因の場合がある。 分かりやすい理由を挙げるなら、 ルートでないプロセスはシグナルを送れないプロセスはトレースできないし、 set-user-ID/set-group-ID プログラムを実行しているプロセスはトレースできない。 または、プロセスはすでにトレース中である、 または init(8) プロセス (PID が 1) である。
ESRCH
指定したプロセスが存在しない。 または、指定したプロセスは呼び出したプロセスが 現在トレース中の子プロセスではない。 または、指定したプロセスが停止していない (停止していることが必要な要求の場合)。

準拠

SVr4, 4.3BSD.

注意

ptrace() の引き数は上のようなプロトタイプに基づいて解釈されるが、 glibc では、現在のところ ptrace() は request 引き数だけが固定の可変長引き数関数として 宣言されている。 これは必要なければ残りの引き数は省略可能であることを意味するが、 それは gcc(1) の明文化されていない動作を利用していることになる。

init(8) すなわち PID が 1 のプロセスはトレースすることができない。

メモリや USER 領域の内容や配置は OS ごと、アーキテクチャごとに 非常に依存する。 オフセットが指定された場合、返されるデータは struct user の定義と完全に一致しないこともありえる。

「ワード (word) 」の大きさは OS によって決まる。 (例えば、32 ビットの Linux では 32 ビットである、など。)

トレースすることによってトレースされるプロセスの動作に些細な違いが 起こることがある。例えば、プロセスが PTRACE_ATTACH によって接続された場合には、そのプロセスが停止した時でも本来の親は wait(2) を使って通知を受けることができず、新しい親が効率よく この通知を真似る方法もない。

親プロセスが PTRACE_EVENT_* がセットされたイベントを受信した場合、 子プロセスは通常通りのシグナル配送が行われる状態にない。 つまり、親プロセスが、 シグナルにより ptrace(PTRACE_CONT) を行ったり、 ptrace(PTRACE_KILL) を行ったりできないということである。 こららのメッセージの受信後は、子プロセスを終了 (kill) するのに、 シグナル SIGKILL を指定して kill(2) を行う方法を代わりに使用できる。

このマニュアルは現在の Linux における ptrace() コールの動作について記述している。他の Unix では その動作は著しく異なる。 いかなる場合も ptrace() を使うと OS やアーキテクチャに非常に依存したものになる。

SunOS のマニュアル・ページには ptrace() は「独特で不可解」と記述されており、まさしくそうである。 Solaris 2 では proc ベースの デバッグのインターフェースとして ptrace() の上位互換関数が実装され、より強力で一貫性のあるものとなっている。

バグ

カーネル 2.6 のヘッダがインストールされたホストでは、 PTRACE_SETOPTIONS はカーネル 2.4 のヘッダとは異なる値で宣言される。 このため、カーネル 2.6 のヘッダでコンパイルされたアプリケーションは カーネル 2.4 では正しく動作しない。 この問題は、 PTRACE_SETOPTIONS が定義されていた際は、 PTRACE_SETOPTIONSPTRACE_OLDSETOPTIONS に定義し直すことで対処できる。