[Linux] Наблюдение за процессами при помощи strace

Привет, %username%! Поговорим о такой болезни как вуайеризм. Подглядывать мы будем за процессами в Linux, а использовать для столь интимного дела мы будем утилиту strace.

Что это и чем его едят

strace — это утилита, присутствующая во многих дистрибутивах Linux по умолчанию, которая может использоваться в диагностических, учебных или отладочных целях. При помощи strace вы иной раз можете избавить себя от лишней головной боли, когда необходимо отыскать причину возникающего сбоя, при этом не имея доступа к исходным кодам. Помимо этого, strace может использоваться при отправке багрепортов разработчикам. Используя этот инструмент вы сможете воочию увидеть, что делает программа.

strace — это бесплатная утилита, распространяемая под BSD-лицензией. Изначально она была написана Paul Kranenburg для SunOS и называлась trace. Затем усилиями Branko Lankester она была портирована на Linux, включая поддержку специфики Linux-ядра. Затем, в 1993 году, версии strace для SunOS и Linux были объединены в одну, был добавлен некоторый функционал truss из SVR4. В итоге получившийся винегрет назвали strace, который теперь работает на многих UNIX-платформах. Сегодня разработкой утилиты занимаются Wichert Akkerman и Roland McGrath.

В самом простом варианте strace запускает переданную команду с её аргументами и выводит в стандартный поток ошибок все системные вызовы команды. Давайте разберём опции утилиты, с помощью которых можно управлять её поведением:

  • -i - выводить указатель на инструкцию во время выполнения системного вызова;
  • -k - выводить стек вызовов для отслеживаемого процесса после каждого системного вызова;
  • -o - выводить всю информацию о системных вызовах не в стандартный поток ошибок, а в файл;
  • -q - не выводить сообщения о подключении о отключении от процесса;
  • -qq - не выводить сообщения о завершении работы процесса;
  • -r - выводить временную метку для каждого системного вызова;
  • -s - указать максимальный размер выводимой строки, по умолчанию 32;
  • -t - выводить время суток для каждого вызова;
  • -tt - добавить микросекунды;
  • -ttt - добавить микросекунды и количество секунд после начала эпохи Unix;
  • -T - выводить длительность выполнения системного вызова;
  • -x - выводить все не ASCI-строки в шестнадцатеричном виде;
  • -xx - выводить все строки в шестнадцатеричном виде;
  • -y - выводить пути для файловых дескрипторов;
  • -yy - выводить информацию о протоколе для файловых дескрипторов;
  • -c - подсчитывать количество ошибок, вызовов и время выполнения для каждого системного вызова;
  • -O - добавить определённое количество микросекунд к счетчику времени для каждого вызова;
  • -S - сортировать информацию выводимую при опции -c. Доступны поля time, calls, name и nothing. По умолчанию используется time;
  • -w - суммировать время между началом и завершением системного вызова;
  • -e - позволяет отфильтровать только нужные системные вызовы или события;
  • -P - отслеживать только системные вызовы, которые касаются указанного пути;
  • -v - позволяет выводить дополнительную информацию, такую как версии окружения, статистику и так далее;
  • -b - если указанный системный вызов обнаружен, трассировка прекращается;
  • -f - отслеживать также дочерние процессы, если они будут созданы;
  • -ff - если задана опция -o, то для каждого дочернего процесса будет создан отдельный файл с именем имя_файла.pid.
  • -I - позволяет блокировать реакцию на нажатия Ctrl+C и Ctrl+Z;
  • -E - добавляет переменную окружения для запускаемой программы;
  • -p - указывает pid процесса, к которому следует подключиться;
  • -u - запустить программу, от имени указанного пользователя.

Вы знаете основные опции strace, но чтобы полноценно ею пользоваться, нужно ещё разобраться с системными вызовами, которые используются чаще всего. Мы не будем рассматривать все, а только основные. Многие из них вы уже и так знаете, потому что они называются так же, как и команды в терминале:

  • fork - создание нового дочернего процесса;
  • read - попытка читать из файлового дескриптора;
  • write - попытка записи в файловый дескриптор;
  • open - открыть файл для чтения или записи;
  • close - закрыть файл после чтения или записи;
  • chdir - изменить текущую директорию;
  • execve - выполнить исполняемый файл;
  • stat - получить информацию о файле;
  • mknod - создать специальный файл, например, файл устройства или сокет;

А теперь разберём примеры strace Linux.

Запуск

Системный вызов — это своего рода «обращение» программы к ядру ОС с просьбой выполнить то или иное действие. Необходимость в таких вызовах обусловлена тем, что процессы не могут напрямую взаимодействовать с системой (представьте, что было бы, если бы каждая программа, например, выделяла себе сколько угодно памяти или могла читать и писать какие угодно файлы!).

Работа strace заключается в отслеживании того, какие системные вызовы делает указанный процесс, а также какие сигналы он получает. Вообще, возможна ситуация, когда процесс не делает ни одного системного вызова. В этом случае, естественно, strace вам ничего не “отследит”.

В общем случае запуск strace выглядит так:

strace program_name

Утилита запустит программу program_name и будет выводить в поток стандартного вывода сообщения о выполняемых системных вызовах. Зачастую засорение стандартного вывода сообщениями трассировки нежелательно, поскольку в нём будет трудно отыскать то, что выводит сам процесс, поэтому лучше перенаправить вывод strace в отдельный файл, который потом анализировать:

strace -o trace_output.txt program_name

Некоторые текстовые редакторы, например Vim, имеют цветовую подсветку вывода strace, что значительно помогает при анализе больших файлов с текстами трассировки.

Ещё, как вариант, можно запускать strace для трассировки уже запущенного процесса. Для этого необходимо знать PID нужного процесса и передать его в качестве параметра опции -p утилиты:

strace -o trace_output.txt -p 1234

Анализ вывода

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

strace -o /home/user/tmp/strace.log /bin/ls

Вывод файла достаточно велик, поэтому здесь не выкладываю. Думаю, кому интересно, сами попробуют на своей системе. Рассмотрим лишь несколько строк в качестве примера.

execve("/bin/ls", ["/bin/ls"], [/* 37 vars */]) = 0
brk(0)                                  = 0x9841000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb779e000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=78866, ...}) = 0
mmap2(NULL, 78866, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb778a000
close(3)                                = 0

Структура каждой строки вывода strace следующая. Первым идёт имя системного вызова. Затем в круглых скобках выводится список параметров, переданных вызову. И последним, после знака равенства, отображается код завершения системного вызова. Подробную документацию по каждому системному вызову в случае необходимости можно найти на соответствующих man-страницах второго раздела.

Резюме

strace может оказаться (и оказывается!) весьма полезной утилитой как для программистов, так и для системных администраторов, помогая отыскать причины “падений” или некорректной работы программ с закрытыми или недоступными исходными кодами.


Обсудить статью в чате TG или в Slack