Как отследить системные вызовы, выполненные процессом с помощью strace в Linux
Бывают случаи, когда полезно проверить, что делает работающее приложение под капотом и какие системные вызовы оно выполняет во время выполнения. Для выполнения такой задачи в Linux мы можем использовать утилиту strace. В этой статье мы увидим, как его установить, и узнаем об основных принципах его использования.
В этом уроке вы узнаете:
- Как установить strace
- Использование strace для трассировки системных вызовов, выполненных процессом
- Как фильтровать специфические системные звонки
- Как подключиться к уже запущенному процессу
- Как создать сводку системных звонков
Как отследить системные вызовы, выполненные процессом с помощью strace в Linux
Требования к программному обеспечению и используемые условные обозначения
Установка
Несмотря на то, что утилита strace не установлена по умолчанию, она доступна в официальных репозиториях всех основных дистрибутивов Linux; это означает, что мы можем очень легко установить ее с помощью нашего любимого менеджера пакетов.
Например, если мы работаем на Fedora (или любом другом дистрибутиве семейства Red Hat), мы должны использовать dnf:
sudo dnf install strace
Если нам удобнее использовать Debian или дистрибутивы на основе Debian, такие как Ubuntu или Linux Mint, мы можем использовать apt для достижения того же результата:
sudo apt install strace
Если мы предпочитаем Arch Linux, мы можем использовать pacman для установки приложения, которое доступно в дополнительном репозитории:
sudo pacman -S strace
Установив программное обеспечение, мы можем двигаться дальше и увидеть некоторые примеры его использования.
Знакомство с strace
Как мы уже говорили, strace — это инструмент, используемый для отслеживания системных вызовов, совершаемых запущенным процессом, и получаемых им сигналов. Системные вызовы являются фундаментальным интерфейсом между приложением и ядром Linux; Когда мы используем Strace, имена вызовов, выполненных процессом, вместе с их аргументами и возвращаемыми значениями отображаются в STDERR (стандартный дескриптор файла ошибок).
Давайте рассмотрим базовое использование strace, чтобы ознакомиться с его выводом. В самом простом использовании мы вызываем strace, за которым следует программа, которую мы хотим выполнить, и поведение, которое мы хотим проанализировать. Для этого примера мы просто скопируем файл с помощью команды cp:
strace cp ~/.bashrc bashrc
Вывод команды довольно длинный, и, конечно, здесь мы не можем его подробно анализировать; Давайте просто посмотрим на первую строку. Каждая строка в выходных данных strace содержит:
- Имя системного вызова
- Аргументы, передаваемые системе, заводятся в круглых скобках
- Возвращаемое значение системного вызова
Первый системный вызов, который мы видим в выводе, — execve
. Этот вызов используется для выполнения программы с указанным массивом аргументов. Первый аргумент, принимаемый execv
, — это путь к файлу, который мы хотим выполнить; второй — массив строк, представляющий аргументы, которые будут переданы программе (первый аргумент, по соглашению, является именем самой программы).
В нашем случае, как и ожидалось, вызывается двоичный файл /usr/bin/cp
, а массив аргументов, передаваемых вызову: имя программы (cp), пути к источнику и назначению:
execve("/usr/bin/cp", ["cp", "/home/egdoc/.bashrc", "bashrc"], 0x7fff53d4e4c0 /* 46 vars */) = 0
Нотация /* 46 vars */
означает, что 46 переменных были унаследованы от вызывающего процесса (в функции execv
окружение берется из переменной внешней среды
). Наконец, у нас есть возвращаемое значение, которое в данном случае равно 0
(на самом деле семейство функций exec
возвращает значение только в случае возникновения ошибки).
Фильтрация только определенных системных вызовов
При использовании strace иногда может потребоваться отслеживать только конкретные системные вызовы, сделанные процессом. В таких ситуациях мы можем использовать опцию -e
, за которой следует выражение, указывающее, какие системные вызовы должны быть трассированы. Предположим, мы выполняем ту же команду, которую использовали в предыдущем примере, но хотим, чтобы в выводе отображались только прочитанные
системные вызовы, мы бы выполнили:
strace -e read cp ~/.bashrc bashrc
Как и ожидалось, выдаются только вызовы чтения
:
Вывод команды "strace -e read cp ~/.bashrc bashrc " Кстати, системный вызов read
системный вызов принимает три аргумента: первый - это style="font-size: inherit; ">файловый дескриптор связанный с файлом, который следует прочитать; второй - the style="font-size: inherit;">buffer в который следует читать файл, и третий - это style="font-size: inherit;">количество байтов , который следует прочитать. При успешном выполнении функция возвращает количество байт read из файла, как мы можем наблюдать в выводе выше.
Присоединение strace к выполняющемуся процессу
До сих пор мы вызывали strace, передавая ему команду для выполнения и сохранения trace; что, если мы хотим отследить существующий и уже запущенный процесс? В этом случае мы должны вызвать strace с параметром -p
(или --attach
) и передать PID (Process Id) процесса, к которому мы хотим его присоединить.
Чтобы найти PID программы, среди прочих решений, можно воспользоваться утилитой pidof. Для примера мы прикрепим strace к работающему экземпляру gnome-terminal-server:
pidof gnome-terminal-server
121316
Команда pidof вернула 121316
, который является PID gnome-terminal-server. Зная это, мы можем присоединить strace к процессу:
strace -p 121316
Приведенная выше команда изначально вернет что-то вроде:
Вывод команды "strace -p 121316" Вышеуказанный (усеченный) вывод будет обновляться "на лету" по мере выполнения системных вызовов. Чтобы "отсоединить" style="font-size: inherit; ">strace мы можем просто нажать Ctrl+C
на клавиатуре; мы получим уведомление о the "detach ", но отслеживаемый процесс продолжит выполняться:
strace: Process 121316 detached
Сигналы трассировки
Благодаря strace мы также можем наблюдать, когда процесс получает сигнал и как он реагирует на него. Позвольте мне это продемонстрировать. Сначала мы запускаем в качестве top долго работающий процесс, который представляет собой монитор процесса:
top
Затем мы прикрепляем к нему strace после получения его PID, который в данном случае равен 44825
:
strace -p 44825
На этом этапе strace начинает отслеживать не только системные вызовы, но и принимаемые им сигналы. Чтобы доказать это, мы отправляем SIGTERM на PID 44825
:
kill 44825
Как и ожидалось, событие сообщается в выходных данных strace:
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=44888, si_uid=1000} ---
В приведенном выше выходном si_signo указан номер доставляемого сигнала (SIGTERM=15), si_code содержит код, определяющий причину сигнала (SI_USER=0): в данном случае сигнал был сгенерирован пользовательским процессом. Поля si_pid и si_uid сообщают, соответственно, PID процесса отправки и его UID.
Сохранение выходных данных strace в файл
Если мы используем опцию -o
(сокращение от --ouput
) при запуске strace, мы можем перенаправить его вывод в файл, передав путь в качестве аргумента, например:
strace -p 121316 -o strace_output
strace: Process 121316 attached
Файл strace_output
будет создан, и в него будет записан вывод strace. Чтобы следить за обновлением файла, мы можем использовать tail: обычно эта команда считывает последние 10 строк файла и завершает работу, но если мы вызовем ее с опцией -f
(сокращение от --follow
), мы можем наблюдать, как добавляется новое содержимое:
tail -f strace_output
Печать сводки системных вызовов
Утилита strace поставляется с очень полезной функцией: возможностью генерировать сводку всех системных вызовов, выполненных указанным процессом. Если мы хотим создать такой отчет, все, что нам нужно сделать, это вызвать программу с опцией -c
или --summary-only
. Возьмем в качестве примера команду cp, которую мы использовали ранее:
strace -c cp ~/.bashrc bashrc
Приведенная выше команда сгенерирует этот отчет:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
25.71 0.000298 7 38 13 openat
19.24 0.000223 4 51 mmap
11.48 0.000133 4 28 close
9.92 0.000115 57 2 1 newfstatat
7.94 0.000092 10 9 mprotect
6.99 0.000081 3 25 fstat
2.85 0.000033 3 11 read
2.76 0.000032 16 2 munmap
2.50 0.000029 14 2 statfs
1.90 0.000022 22 1 write
1.55 0.000018 2 8 pread64
1.38 0.000016 8 2 1 access
1.04 0.000012 4 3 brk
0.78 0.000009 4 2 rt_sigaction
0.60 0.000007 7 1 futex
0.52 0.000006 3 2 1 arch_prctl
0.43 0.000005 5 1 rt_sigprocmask
0.43 0.000005 5 1 set_tid_address
0.43 0.000005 5 1 fadvise64
0.43 0.000005 5 1 set_robust_list
0.43 0.000005 5 1 prlimit64
0.26 0.000003 3 1 1 stat
0.26 0.000003 3 1 1 lseek
0.17 0.000002 2 1 geteuid
0.00 0.000000 0 1 execve
------ ----------- ----------- --------- --------- ----------------
100.00 0.001159 5 196 18 total
Как вы можете видеть, поскольку мы создали сводку, обычный вывод strace не отображается. Если мы хотим сгенерировать сводку, но при этом получить обычный вывод программы, мы должны использовать опцию -C
, которая является краткой формой --summary
.
Выводы
В этом уроке мы узнали, как установить и использовать strace, полезную утилиту для отладки и в целом для отслеживания системных вызовов, выполняемых процессом. Мы рассмотрели, как организован вывод strace, как запустить программу и отслеживать системные вызовы, которые она выполняет, как прикрепить strace к уже запущенному процессу и как уведомляются сигналы, полученные процессом; наконец, мы увидели, как создать сводку всех вызовов, сделанных процессом. Здесь мы лишь поверхностно коснулись того, что мы можем сделать с помощью Strace: если вы хотите узнать о нем больше, советуйте, как всегда, прочитать руководство!