コードは子からの通知を考慮していないため、exec
syscall エントリを syscall exit として処理し、syscall exit を syscall entry として処理することになります。そのため、syscall 12 returned
「_ _ _syscall 12 called
-38
ENOSYS
ptrace(2)
マニュアルページに記載されているように:
PTRACE_TRACEME
このプロセスがその親によってトレースされることを示します。このプロセスにシグナル (SIGKILL を除く) が送信されると、プロセスが停止し、wait() を介してその親に通知されます。また、このプロセスによる exec() への後続のすべての呼び出しにより、SIGTRAP がそれに送信され、新しいプログラムが実行を開始する前に、親が制御を取得する機会が与えられます。[...]
あなたが実行していた元のコードは、「私が実行していることを除いて、これexecl("/bin/ls", "ls", NULL);
と同じ」とおっしゃいました。32 ビットではなく x86_64 を使用していて、少なくともメッセージを変更しているため、明らかにそうではありません。
しかし、他にあまり変更を加えていないと仮定すると、最初に が親を起動したときwait()
、それはシステムコールの開始または終了のためではなく、親はまだ実行されptrace(PTRACE_SYSCALL,...)
ていません。代わりに、子が実行したというこの通知が表示されexec
ます (x86_64 では、syscall 59 は ですexecve
)。
コードはそれを syscall エントリとして誤って解釈します。 次にを呼び出しptrace(PTRACE_SYSCALL,...)
、次に親が起こされたときはsyscall エントリ (syscall 12) のためですが、コードはそれを syscall exit として報告します。
この元のケースでは、システムコールの開始/終了は表示されず、追加の通知のみが表示されることに注意してください。これは、親が発生するまでexecve
実行されないためです。ptrace(PTRACE_SYSCALL,...)
システムコールの入口/出口がキャッチされるようにコードを調整すると、観察した新しい動作が表示されます。execve
親は3回起動されます。1 回目はexecve
syscall の開始 ( の使用による) ptrace(PTRACE_SYSCALL,...)
、1 回はexecve
syscall の終了 (これも の使用によるptrace(PTRACE_SYSCALL,...)
)、3 回目はexec
通知 (とにかく発生します) です。
以下は完全な例 (x86 または x86_64 の場合) で、exec
最初に子プロセスを停止することによって、自身の動作を示すように注意します。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#ifdef __x86_64__
#define SC_NUMBER (8 * ORIG_RAX)
#define SC_RETCODE (8 * RAX)
#else
#define SC_NUMBER (4 * ORIG_EAX)
#define SC_RETCODE (4 * EAX)
#endif
static void child(void)
{
/* Request tracing by parent: */
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
/* Stop before doing anything, giving parent a chance to catch the exec: */
kill(getpid(), SIGSTOP);
/* Now exec: */
execl("/bin/ls", "ls", NULL);
}
static void parent(pid_t child_pid)
{
int status;
long sc_number, sc_retcode;
while (1)
{
/* Wait for child status to change: */
wait(&status);
if (WIFEXITED(status)) {
printf("Child exit with status %d\n", WEXITSTATUS(status));
exit(0);
}
if (WIFSIGNALED(status)) {
printf("Child exit due to signal %d\n", WTERMSIG(status));
exit(0);
}
if (!WIFSTOPPED(status)) {
printf("wait() returned unhandled status 0x%x\n", status);
exit(0);
}
if (WSTOPSIG(status) == SIGTRAP) {
/* Note that there are *three* reasons why the child might stop
* with SIGTRAP:
* 1) syscall entry
* 2) syscall exit
* 3) child calls exec
*/
sc_number = ptrace(PTRACE_PEEKUSER, child_pid, SC_NUMBER, NULL);
sc_retcode = ptrace(PTRACE_PEEKUSER, child_pid, SC_RETCODE, NULL);
printf("SIGTRAP: syscall %ld, rc = %ld\n", sc_number, sc_retcode);
} else {
printf("Child stopped due to signal %d\n", WSTOPSIG(status));
}
fflush(stdout);
/* Resume child, requesting that it stops again on syscall enter/exit
* (in addition to any other reason why it might stop):
*/
ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL);
}
}
int main(void)
{
pid_t pid = fork();
if (pid == 0)
child();
else
parent(pid);
return 0;
}
次のような結果が得られます (これは 64 ビットの場合です。システム コール番号は 32 ビットでは異なります。特にexecve
、59 ではなく 11 です)。
子は信号19で停車
SIGTRAP: システムコール 59、rc = -38
SIGTRAP: システムコール 59、rc = 0
SIGTRAP: システムコール 59、rc = 0
SIGTRAP: システムコール 63、rc = -38
SIGTRAP: システムコール 63、rc = 0
SIGTRAP: システムコール 12、rc = -38
SIGTRAP: システムコール 12、rc = 5324800
...
シグナル 19 は明示的SIGSTOP
です。上記のように、子供は3回停止execve
します。その後、他のシステム コールに対して 2 回 (入口と出口)。
のすべての悲惨な詳細に本当に興味があるなら、ptrace()
私が知っている最高のドキュメントは
ソースREADME-linux-ptrace
内のファイルです。strace
「API は複雑で、微妙なクセがあります」....