Почему эта программа ptrace сообщает, что syscall вернул -38? - PullRequest
5 голосов
/ 22 сентября 2011

Это то же самое, что и этот , за исключением того, что я бегу execl("/bin/ls", "ls", NULL);.

Результат явно неправильный, так как каждый системный вызов возвращается с -38:

[user@ test]# ./test_trace 
syscall 59 called with rdi(0), rsi(0), rdx(0)
syscall 12 returned with -38
syscall 12 called with rdi(0), rsi(0), rdx(140737288485480)
syscall 9 returned with -38
syscall 9 called with rdi(0), rsi(4096), rdx(3)
syscall 9 returned with -38
syscall 9 called with rdi(0), rsi(4096), rdx(3)
syscall 21 returned with -38
syscall 21 called with rdi(233257948048), rsi(4), rdx(233257828696)
...

Кто-нибудь знает причину?

UPDATE

Теперь проблема:

execve called with rdi(4203214), rsi(140733315680464), rdx(140733315681192)
execve returned with 0
execve returned with 0
...

execve возвращается 0 дважды , почему?

Ответы [ 3 ]

11 голосов
/ 23 сентября 2011

Код не учитывает уведомление о 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 сложен и имеет тонкие причуды» ....

0 голосов
/ 23 сентября 2011

Я бы сказал, что вы проверяете eax или его 64-битный эквивалент (предположительно rax) для кода возврата системного вызова. Для сохранения этого регистра есть дополнительный слот с именем orig_eax, используемый для перезапуска системных вызовов.

Я довольно много в этом разбираюсь, но не могу за всю жизнь найти свои выводы. Вот несколько связанных вопросов:

Update0

Выглядит снова, кажется, моя память работает правильно. Здесь вы найдете все, что вам нужно здесь, в исходном коде ядра (основной сайт не работает, к счастью, torvalds теперь отражает linux на github).

0 голосов
/ 22 сентября 2011

Вы можете распечатать удобочитаемое описание последней системной ошибки с помощью perror или strerror. Это описание ошибки поможет вам существенно больше.

...