Это хороший способ перехватить системные вызовы?

Я пишу инструмент. Частью этого инструмента будет его способность регистрировать параметры системных вызовов. Хорошо, я могу использовать ptrace для этой цели, но ptrace работает довольно медленно. Более быстрый метод, который пришел мне на ум, состоял в том, чтобы изменить glibc. Но это становится трудным, поскольку gcc волшебным образом вставляет свои встроенные функции в качестве оберток системных вызовов, чем использование кода, определенного в glibc. Использование -fno-builtin также не помогает.

Итак, я придумал идею создания разделяемой библиотеки, которая включает в себя каждую оболочку системных вызовов, например mmap, а затем выполнить регистрацию перед вызовом функции обертки фактического системного вызова. Например, псевдо-код того, как будет выглядеть мой mmap, приведен ниже.

int mmap(...)
{
 log_parameters(...);
 call_original_mmap(...);
 ...
}

Затем я могу использовать LD_PRELOAD для загрузки первой библиотеки. Считаете ли вы, что эта идея будет работать, или я что-то пропущу?

Ответ 1

Никакой метод, который вы, возможно, не придумаете в пользовательском пространстве, не будет работать без проблем с любым приложением. К счастью для вас, уже существует поддержка того, что вы хотите делать в ядре. Kprobes и Kretprobes позволяют вам проверять состояние машины, только предшествующей и следуя системному вызову.

Документация здесь: https://www.kernel.org/doc/Documentation/kprobes.txt

Ответ 2

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

EDIT Я нашел это http://cateee.net/lkddb/web-lkddb/AUDITSYSCALL.html. Ядра Linux: 2.6.6-2.6.39, 3.0-3.4 имеют поддержку для аудита системных вызовов. Это модуль ядра, который должен быть включен. Возможно, вы можете посмотреть на источник этого модуля, если это не запутать.

Ответ 3

Как уже упоминалось, если бинарный файл статически связан, динамический компоновщик пропустит любые попытки перехвата функций с помощью libdl. Вместо этого вы должны рассмотреть возможность запуска процесса самостоятельно и объединить точку входа в функцию, которую вы хотите перехватить.

Это означает запуск самого процесса, перехват его выполнения и переписывание его памяти для размещения инструкции перехода в начале определения функции в памяти для новой функции, которую вы управляете.

Если вы хотите перехватить фактические системные вызовы и не можете использовать ptrace, вам придется либо найти сайт выполнения для каждого системного вызова, либо переписать его, либо вам может потребоваться перезаписать таблицу системных вызовов в памяти и отфильтровывать все, кроме процесса, который вы хотите контролировать.

Ответ 4

Если код, который вы разрабатываете, связан с процессом, иногда вы можете разрабатывать альтернативные реализации без нарушения существующего кода. Это полезно, если вы переписываете важный системный вызов и хотите полностью функциональную систему для его отладки.

В вашем случае вы переписываете алгоритм mmap(), чтобы воспользоваться захватывающей новой функцией (или улучшением с помощью новой функции). Если вы не получите все правильно с первой попытки, отладить систему было бы нелегко: не функционирующий системный вызов mmap() наверняка приведет к не функционирующей системе. Как всегда, есть надежда.

Часто можно сохранить оставшийся алгоритм на месте и построить свою замену сбоку. Вы можете добиться этого, используя идентификатор пользователя (UID) как условие, с помощью которого можно решить, какой алгоритм использовать:

if (current->uid != 7777) {
/* old algorithm .. */
} else {
/* new algorithm .. */
}

Все пользователи, кроме UID 7777, будут использовать старый алгоритм. Вы можете создать специального пользователя с UID 7777 для тестирования нового алгоритма. Это значительно упрощает проверку критического кода, связанного с процессом.