Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016)

Материал из 0x1.tv

Аннотация

Докладчик
Эльвира Хабирова.jpg
Эльвира Хабирова

strace — утилита для отладки программ. Она отображает сделанные отлаживаемым процессом tracee системные вызовы, пришедшие ему сигналы, изменения его состояния и пр.

Вывод strace на данный момент нацелен на человекочитаемость и по этой причине тяжело поддается автоматической обработке.

Кроме того, из-за отсутствия единой системы вывода в выводе могут присутствовать неточности, что еще сильнее усложняет задачу.

Поэтому в рамках GSoC 2016 проблема автоматической обработки была решена разработкой такой единой системы.

Как результат, стало возможным легко встраивать не только подсистемы любого формата вывода, но и дополнительные слои логики.

Видео

on youtube

Слайды

Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016).pdf

Расширенные тезисы

Обзор проблемы

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

На входе могла печататься только часть аргументов (до первого out[1])

Также существует понятие in-out аргумента: такой аргумент указывает на место в памяти с данными, являющимися входными и выходными аргументами. В случае, если это имеет смысл и если указан достаточный уровень подробности вывода, strace копирует из tracee данные, находящиеся по указателям, переданным в аргументах, и печатает их. Этот процесс может быть весьма многоуровневым — в случае передачи массивов указателей на структуры, например.

Дополнительно strace может собирать и печатать статистику по времени выполнения, печатать содержимое iovec и проч.

strace не содержал унифицированных средств по печати информации о системном вызове; каждый декодер самостоятельно обеспечивал тот формат вывода, который считал нужным (за исключением некоторых базовых вещей, таких как вывод строки, флагов, массивов, дескрипторов... Хотя и здесь есть исключения в случае отдельных декодеров, которым требуется нестандартный вывод).

Далее рассмотрим несколько примеров существующих декодеров системных вызовов.

Рассмотрим иллюстрацию, пример отображения информации о системном вызове accept4:

accept4(3, {sa_family=AF_UNIX, sun_path="accept4.socket.connect"}, [110->25], SOCK_NONBLOCK|SOCK_CLOEXEC) = 5


accept4 принимает

  • в качестве второго аргумента указатель на структуру struct sockaddr,
  • в качестве третьего — размер структуры аргумента,
  • и в качестве четвёртого — набор флагов.

В данном случае strace

  • скопировал из tracee структуру struct sockaddr и декодировал её согласно значению поля sa_family (т. е. интерпретировал последующие данные как поле sun_path);
  • также strace прочитал значение по указателю, переданному в аргументе addrlen, причём и на входе, и на выходе, так как это in-out аргумент.
  • Четвёртый аргумент декодирован как набор флагов.

Перейдем к примеру отображения информации о системном вызове setitimer

setitimer(ITIMER_REAL, {it_interval={0, 222222}, it_value={0, 111111}}, NULL) = 0

В качестве второго и третьего аргументов setitimer принимает указатель на struct itimerval, который, в свою очередь, представляет собой пару структур struct timeval.

Заметим, что декодер системного вызова выводит имена полей struct itimerval, но не struct timeval.


На примере отображения информации о системном вызове pwritev с печатью содержимого iovec можно отметить сразу несколько особенностей вывода:

 $ strace -etrace=pwritev -ewrite=1 -s2 ./preadv-pwritev
 ...
 pwritev(1, [{iov_base="01"..., iov_len=3}, {iov_base="34"..., iov_len=5}, ...], 3, 0) = 15
 * 3 bytes in buffer 0
 | 00000  30 31 32                                          012              |
 * 5 bytes in buffer 1
 | 00000  33 34 35 36 37                                    34567            |
 * 7 bytes in buffer 2
 | 00000  38 39 61 62 63 64 65                              89abcde          |
  • Строки и массивы, если их размер превышает указанный в параметре -s (32 по умолчанию), сокращаются и терминируются многоточиями.
  • Для I/O вызовов существует возможность, посредством задания опции -ewrite=1/-eread=1, вывода содержимого данных, передаваемых в этих вызовах, в формате шестнадцатеричного дампа.

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

execve("./execve", ["./execve"], [/* 41 vars */]) = 0

Пример отображения информации о системном вызове prctl с PR_SET_SECCOMP в качестве аргумента option

prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, {len=3,
filter=[BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 0),
BPF_JUMP(BPF_JMP|BPF_K|BPF_JEQ, 0x3, 0, 0x1),
BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW)]}) = 0


То, какие из аргументов arg2, arg3, arg4, arg5 выведет strace и в каком формате, определяется значением первого аргумента option. В данном случае это команда установки BPF-фильтра seccomp. Другой особенностью данного примера является формат вывода команд BPF, которые представляют собой структуры, но для которых в <linux/filter.h> определены макрокоманды для их описания в коде на языке Си; декодер системного вызова определяет по содержимому структур подходящую макрокоманду и печатает соответствующую макрокоманду.

На примере отображения информации о системном вызове mincore отметим формат вывода третьего аргумента vec, который представляет собой массив байт, из каждого из которых значащий только один:

mincore(0x2ba1f6293000, 131073, [11111111111111111111...]) = 0

В примере отображения информации о системном вызове listen интересен формат вывода, используемый для файловых дескрипторов (первый аргумент):

listen(3<TCP:[4490622]>, 1) = 0

А в примере отображения информации о системном вызове \EN{\emph{rt\_setsigprocmask}}}, можно отметить специфический формат, используемый для вывода маски сигналов.

rt_sigprocmask(SIG_SETMASK, NULL, [HUP INT QUIT ALRM TERM], 8) = 0


Идея структурированного вывода

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

Strace-lp0-conversion.svg

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

Strace-scheme.svg

Декодер заполняет это внутреннее представление, и оно может потом быть выведено независимо.

Переход к структурированному выводу позволяет избежать многочисленных однотипных вызовов печати (запятые, скобки, знаки равенства) и связанных с этим потенциальных багов.

Разделение процесса декодирования и вывода позволяет внедрить возможности, которые ранее были затруднительны в реализации.

Например:

  • Наконец-то можно корректно реализовать секретную опцию -z, которая выводит только успешно завершившиеся системные вызовы. Ранее было неясно, как обходить случаи, когда системный вызов не мог быть полностью декодирован на входе, а также когда трассируемых процессов несколько.
  • Изначально при реализации структурированного вывода было принято решение сохранять имена аргументов; ранее такой информации декодеры не предоставляли. Это позволило реализовать опцию -N, которая позволяет выводить имена аргументов для некоторых[2] или всех системных вызовов. В существовавшей схеме вывода для добавления подобной функциональности потребовалось бы существенное усложнение логики всех декодеров и маловероятно, что в исходной схеме вывода такая возможность сама по себе была бы реализована.
  • Также добавлена возможность привязки комментариев к аргументам, которая может быть полезна для системных вызовов, вывод аргументов которых зависит от значений аргументов.
  • Существенно упростилась поддержка out и in-out аргументов. Теперь декодер системного вызова сохраняет всю доступную информацию на входе и обновляет её на выходе, что гораздо более однообразно, чем использовавшаяся ранее сложная логика. Раньше аргументы системного вызова на входе декодировались только до первого out аргумента; иногда приходилось сохранять значения последующих in аргументов для использования на выходе[3].
  • И главное, выделение процесса форматирования вывода в отдельную сущность позволило реализовать то, ради чего затевался переход на структурированный вывод — форматирование вывода в машиночитаемом виде. В качестве примера такого формата был выбран JSON. Заложенная архитектура позволяет просто реализовывать дополнительные машиночитаемые и человекочитаемые синтаксисы. В качестве возможных вариантов — pcap/pcapng[4], YAML.


Текущее состояние

Работы по переходу на структурированный вывод ещё не закончены. К настоящему моменту стабилизирован API структурного вывода[5], сконвертировано примерно 65% декодеров системных вызовов, реализованы выводы в форматах legacy и JSON, реализована базовая проверка, является ли вывод в формате JSON корректным JSON (проверка на отсутствие остатков непосредственных вызовов функций печати). Регулярно производится git rebase на master-ветку strace. Из несделанного — помимо оставшихся декодеров системных вызовов[6] — отсутствие тестов для формата JSON. Прогресс разработки можно отслеживать в соответствующем репозитории[7].

Примечания и отзывы

  1. out аргумент — аргумент, который указывает на участок памяти, в котором находятся данные, являющиеся выходными для вызываемой функции.
  2. В первую очередь это актуально для тех системных вызовов, в которых семантика одних аргументов зависит от значения других; см. ipc, fcntl, prctl, keyctl, futex, quotactl.
  3. см. prctl в process.c, accept/accept4 в net.c.
  4. PcapNg draft specification https://github.com/pcapng/pcapng
  5. Structured output readme. https://github.com/lineprinter/strace/blob/structured/README-structured.md
  6. Среди которых можно отметить системный вызов ioctl.
  7. Structured output branch repository. https://github.com/lineprinter/strace/tree/structured
Действительно структурированный вывод в strace (Эльвира Хабирова, OSSDEVCONF-2016)!.jpg

Plays:115   Comments:0