Перехватить только системный вызов с помощью PTRACE_SINGLESTEP

У нас есть школьный проект, где нам нужно перекодировать strace.

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

Вот код, который я использую, может быть, кто-нибудь поможет мне разобраться, что с ним не так:

pid_t child;
long orig_eax;
user_regs_struct regs;

child = fork();
if (child == 0) {
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    execve("/home/architek/a.out", {"/home/architek/a.out", NULL}, envp);
} else {
    waitpid(child, &status, 0);
    while (WIFSTOPPED(status)) {
        orig_eax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
        ptrace(PTRACE_GETREGS, child, NULL, &regs);
        call_printer(&regs, child);
        ptrace(PTRACE_SINGLESTEP, child, 0, 0);
        waitpid(child, &status, 0);
    }
}

person Architek    schedule 11.04.2021    source источник
comment
Вы прочитали всю (загадочную) документацию ptrace (2) ? См. Также Расширенное программирование Linux и системные вызовы (2). Кстати, strace является открытым исходным кодом, вы можете изучить его исходный код. Приведите минимальный воспроизводимый пример   -  person Basile Starynkevitch    schedule 11.04.2021
comment
Возможно, вам придется заглянуть внутрь /usr/include/x86_64-linux-gnu/sys/user.h   -  person Basile Starynkevitch    schedule 11.04.2021
comment
Я прочитал ptrace (2) и не нашел ни одного запроса, который может быть полезен, или, может быть, я не видел, как он может быть полезен, я нашел PTRACE_SYSEMU, но он тоже запрещен темой, я читал немного исходного кода strace, почитаю больше, может быть, найду что-нибудь полезное; что касается структуры в user.h, я не могу найти ничего, что помогло бы мне выяснить, остановился ли трассируемый на системном вызове или нет   -  person Architek    schedule 11.04.2021


Ответы (1)


Если вы не можете использовать PTRACE_SYSCALL, чтобы остановить дочерний элемент прямо перед / после системного вызова, вам придется вручную определять, когда он вот-вот произойдет. Я сомневаюсь, что проверка исходного кода strace могла бы помочь, поскольку strace, скорее всего, использует PTRACE_SYSCALL, и нет причин для ручного декодирования инструкций.

Предполагая, что вы работаете с x86-64, вот как это можно сделать:

  1. Используя PTRACE_SINGLESTEP, продолжайте выполнять одну инструкцию за раз.
  2. В каждой инструкции используйте PTRACE_PEEKTEXT для извлечения следующей инструкции, на которую указывает указатель инструкции.
  3. Проверьте, является ли инструкция syscall, сравнив байты с кодом операции syscall, который равен два байта: 0x0f 0x05. Поскольку x86 является прямым порядком байтов, это означает проверку того, имеет ли возвращаемое значение ptrace(PTRACE_PEEKDATA, ...) два младших байта, установленных на 0x050f.

ПРИМЕЧАНИЕ. Если вы используете другую архитектуру или также хотите обнаруживать 32-разрядные системные вызовы, вы можете просто проверить наличие других / дополнительных значений на шаге 3. В Linux x86-64 их несколько. способы выполнить системный вызов с разными кодами операции. Например, 32-разрядные системные вызовы в Linux выполняются через int 0x80 (код операции 0xcd 0x80). Список можно найти в другом моем ответе.

Вот пример:

#include <errno.h>

long opcode;

// ...

waitpid(child, &status, 0);

while (WIFSTOPPED(status)) {
    ptrace(PTRACE_GETREGS, child, NULL, &regs);

    errno = 0;
    opcode = ptrace(PTRACE_PEEKTEXT, child, regs.rip, 0);
    if (opcode == -1 && errno != 0) {
        perror("ptrace(PTRACE_PEEK_DATA) failed");
        exit(1);
    }

    if ((unsigned long)opcode & 0xffff == 0x050f) {
        // Child about to execute a syscall instruction,
        // check the registers to know more...
    }

    ptrace(PTRACE_SINGLESTEP, child, 0, 0);
    waitpid(child, &status, 0);
}
person Marco Bonelli    schedule 11.04.2021
comment
Привет, спасибо за вашу помощь, он отлично работает, чтобы остановить трассировку перед системным вызовом (мне просто пришлось использовать объединение с long и char [8], чтобы получить код операции, потому что & не работает), это то же самое поймать после системного вызова, или мне нужно найти другой способ? - person Architek; 11.04.2021
comment
@Architek после идентификации системного вызова, просто выполните один-единственный PTRACE_SINGLESTEP, системный вызов будет выполнен, и дочерний процесс остановится сразу после следующей инструкции. - person Marco Bonelli; 11.04.2021