该代码不考虑exec
来自孩子的通知,因此最终将系统调用条目作为系统调用退出处理,并将系统调用退出作为系统调用条目处理。这就是为什么您会在“ syscall 12 returned
”之前看到“ ”syscall 12 called
等(这-38
是ENOSYS
内核的系统调用入口代码将其作为默认返回值放入 RAX。)
正如ptrace(2)
手册页所述:
PTRACE_TRACEME
指示此进程将由其父进程跟踪。传递给此进程的任何信号(SIGKILL 除外)都将导致它停止并通过 wait() 通知其父进程。此外,此进程对 exec() 的所有后续调用都将导致向其发送 SIGTRAP,从而使父进程有机会在新程序开始执行之前获得控制权。[...]
您说您正在运行的原始代码“与此相同,只是我正在运行execl("/bin/ls", "ls", NULL);
”。好吧,显然不是,因为您使用的是 x86_64 而不是 32 位,并且至少更改了消息。
但是,假设你没有改变太多,第一次wait()
唤醒父级时,它不是用于系统调用进入或退出 - 父级尚未执行ptrace(PTRACE_SYSCALL,...)
。相反,您会看到这个孩子执行了exec
(在 x86_64 上,系统调用 59 是execve
)的通知。
该代码错误地将其解释为系统调用条目。 然后它调用ptrace(PTRACE_SYSCALL,...)
,下次唤醒父级时,它是系统调用条目(系统调用 12),但代码将其报告为系统调用退出。
请注意,在这种原始情况下,您永远不会看到execve
系统调用进入/退出 - 只有附加通知 - 因为父级ptrace(PTRACE_SYSCALL,...)
直到它发生之后才会执行。
如果您确实安排代码以便execve
捕获系统调用进入/退出,您将看到您观察到的新行为。父级将被唤醒三次:一次用于execve
系统调用进入(由于使用ptrace(PTRACE_SYSCALL,...)
,一次用于execve
系统调用退出(也是由于使用 )ptrace(PTRACE_SYSCALL,...)
,第三次用于exec
通知(无论如何都会发生)。
exec
这是一个完整的示例(对于 x86 或 x86_64),它通过首先停止子节点来注意显示自身的行为:
#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
是 11,而不是 59):
孩子因信号 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
然后两次(进入和退出)用于其他系统调用。
如果您对 的所有血腥细节真的很感兴趣,那么ptrace()
我所知道的最好的文档就是
源README-linux-ptrace
文件中的文件strace
。正如它所说,“API 很复杂并且有微妙的怪癖”......