Код не учитывает уведомление о exec
от дочернего элемента, поэтому он обрабатывает запись syscall как выход syscall, а выход syscall как запись syscall. Вот почему вы видите "syscall 12 returned
" перед"syscall 12 called
" и т. Д. (-38
- это ENOSYS
, который вводится в RAX как возвращаемое значение по умолчанию с помощью кода входа системного вызова ядра.)
Как ptrace(2)
man-страница гласит:
PTRACE_TRACEME
Указывает, что этот процесс должен отслеживаться его родителем. Любой сигнал (кроме SIGKILL), доставленный этому процессу, приведет к его остановке и уведомлению его родителя с помощью wait (). Кроме того, все последующие вызовы exec () этого процесса приведут к отправке SIGTRAP на него , что даст родителю возможность получить контроль до начала выполнения новой программы. [...]
Вы сказали, что исходный код, который вы выполняли, был "таким же, как , этот , за исключением того, что я использую execl("/bin/ls", "ls", NULL);
". Ну, это явно не так, потому что вы работаете с x86_64, а не с 32-битной версией, и по крайней мере изменили сообщения.
Но, предполагая, что вы не слишком сильно изменились, первый раз, когда wait()
разбудит родителя, это не для входа или выхода из системного вызова - родитель не выполнил ptrace(PTRACE_SYSCALL,...)
еще. Вместо этого вы видите это уведомление о том, что ребенок выполнил exec
(на x86_64 syscall 59 равен execve
).
Код неправильно интерпретирует это как запись системного вызова. Затем он вызывает ptrace(PTRACE_SYSCALL,...)
, и в следующий раз, когда родитель пробуждается, он равен для записи системного вызова (системный вызов 12), но код сообщает об этом как о выходе системного вызова.
Обратите внимание, что в этом исходном случае вы никогда не увидите вход / выход системного вызова execve
- только дополнительное уведомление - потому что родитель не выполняет ptrace(PTRACE_SYSCALL,...)
до тех пор, пока это не произойдет.
Если вы делаете , расположите код так, чтобы вход / выход системного вызова execve
были перехвачены, вы увидите новое поведение, которое вы наблюдаете. Родитель будет просыпаться три раза: один раз для execve
записи системного вызова (из-за использования ptrace(PTRACE_SYSCALL,...)
, один раз для execve
выхода из системного вызова (также из-за использования ptrace(PTRACE_SYSCALL,...)
и третьего время для уведомления 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
равно 11, а не 59):
Child stopped due to signal 19
SIGTRAP: syscall 59, rc = -38
SIGTRAP: syscall 59, rc = 0
SIGTRAP: syscall 59, rc = 0
SIGTRAP: syscall 63, rc = -38
SIGTRAP: syscall 63, rc = 0
SIGTRAP: syscall 12, rc = -38
SIGTRAP: syscall 12, rc = 5324800
...
Сигнал 19 является явным SIGSTOP
; ребенок останавливается три раза на execve
, как только что описано выше; затем дважды (вход и выход) для других системных вызовов.
Если вам действительно интересны все ужасные детали ptrace()
, лучшая документация, о которой я знаю, это
Файл README-linux-ptrace
в источнике strace
. Как говорится, «API сложен и имеет тонкие причуды» ....