The book of Magnus

IT-заметки и знания

Strace

Tags = [ Linux, Soft, Strace ]

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

Основные возможности strace:

  1. Трассировка системных вызовов: strace перехватывает и регистрирует все системные вызовы, выполняемые процессом. Это может включать открытие файлов, чтение и запись данных, создание новых процессов и другие операции.
  2. Детальный вывод: Инструмент предоставляет подробный вывод о каждом системном вызове, включая его параметры, возвращаемые значения и ошибки. Например, можно увидеть, какие файлы открываются, какие данные передаются по сети и т.д.
  3. Отладка: strace полезен для отладки программ, поскольку он позволяет разработчикам видеть, как их код взаимодействует с ядром. Это может помочь обнаружить ошибки или неправильное использование системных вызовов.
  4. Анализ производительности: Поскольку strace показывает, какие системные вызовы выполняются и сколько времени они занимают, его можно использовать для анализа производительности программ и выявления узких мест.
  5. Диагностика проблем: Если программа ведет себя неожиданным образом, strace может помочь понять, что происходит внутри. Например, если программа не может открыть файл, strace покажет, какой именно файл программа пытается открыть и почему это не удается.

Strace может помочь в следующих ситуациях:

  • если приложение отказывается работать из-за проблем с правами;
  • если приложение не запускается из-за отсутствия какого-нибудь нужного файла;
  • в некоторых случаях с помощью strace быстрее, чем с помощью tcpdump, можно обнаружить проблемы с сетевыми прогами;
  • при проблемах с физическим или псевдоустройством (типа /dev/random или /dev/ audit) strace покажет последний незавершенный вызов;
  • если надо отследить все файлы, к которым обращается приложение в процессе работы. Это может быть полезным, например, для составления профиля AppArmor или переноса приложения в среду chroot. В простейшем случае вызов strace выглядит следующим образом:
$ strace uname
execve("/bin/uname", ["uname"], [/* 36 vars */]) = 0
brk(0) = 0x1ed2000
access("/etc/ld.so.nohwcap", F_OK) = -1
ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0x7fb79f08a000
access("/etc/ld.so.preload", R_OK) = -1
ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=133660, ...}) = 0
### Попытка получить доступ к большому количеству
файлов, в основном из каталога /usr/
lib/locale/ru_RU.utf8
uname({sys="Linux", node="adept-laptop",
...}) = 0

По умолчанию весь вывод strace отправляет в stderr, что далеко не всегда удобно. Попросить strace писать вывод в файл можно с помощью опции '-o':

$ strace -o uname.strace uname

Первый системный вызов — execve: запуск файла на выполнение. В скобках передается команда с аргументами (если они есть) и количество переменных окружения, переданных процессу. По умолчанию strace не показы вает сами переменные окружения, но его можно попросить выводить более подробную информацию с помощью опции '-v'. Вызов возвратил 0 — значит все ok. В противном случае значение было бы -1.

Следующий интересный системный вызов — access: проверка прав пользователя на файл. В данном случае тестируется существование файла (о чем говорит режим проверки F_OK). На третьей строчке системный вызов вернул значение -1 (ошибка) и вывел ошибку ENOENT (No such file or directory). Это нормально, так как этот файл всего лишь служит для указания линковщику на использование стандартных неоптимизированных версий библиотек.

Как правило, с помощью вызова access проверяются только права на файл или существование самого файла, без каких-либо последующих манипуляций над файлом. Манипуляции над файлом всегда начинаются с системного вызова open, открывающего файл в одном из режимов (O_RDONLY, O_WRONLY или O_RDWR).

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

После открытия файла вызовом open происходит его чтение вызовом read или запись вызовом write. Оба вызова принимают файловый дескриптор, а возвращают количество прочитанных/записанных байт.

Вызов fstat предназначен для получения информации о файле (номер inode, uid, gid и т.д.)

Самый главный вызов в листинге выше — uname, который позволяет получить информацию о текущем ядре. Если трассировка uname занимает всего сотню строк, то трассировка серьезного приложения легко может занимать несколько тысяч строк. Читать такой лог — не самое большое удовольствие. Поэтому иногда лучше записывать в лог только определенные вызовы. Например, чтобы отследить все вызовы open и access (а на них следует обращать внимание в первую очередь при проблемах с запуском приложения):

$ strace -e trace=open,access \   -o strace.log uname

Вместо перечисления всех нужных вызовов можно использовать классы, состоящие только из специализированных вызовов: file, process, network, signal или ipc. Также можно писать в лог все вызовы, кроме одного. Например, чтобы исключить вызов mmap:

$ strace -e trace=\!mmap -o strace.log uname

К сожалению, исключить из вывода сразу несколько вызовов не получится. Некоторые приложения в процессе работы любят наплодить большое количество дочерних процессов. По умолчанию strace игнорирует дочерние процессы, но это поведение можно изменить с помощью опции '-f'.

Если вывод strace пишется в лог, то удобно использовать опцию '-ff', которая заставляет strace писать трассировку каждого процесса в отдельный лог вида filename.PID. Еще одна весьма полезная возможность strace: с помощью опции '-p' и указания PID можно проводить трассировку работающего процесса. Можно даже соединиться сразу с несколькими процессами, указав опцию '-p' несколько раз. Вот такая конструкция запустит трассировку всех процессов apache:

# strace -f $(pidof apache2 | sed 's/\([0-9]*\)/\-p \1/g')

Примеры использования

Проблемы с правами

Предположим, что после обновления сервера из PHP с помощью обычной функции mail перестали отправляться письма, а влогах пусто. Запускаем трассировку:

# strace -f -o /tmp/apache2.strace \   /etc/init.d/apache2 start

После того, как скрипт отправки почты был несколько раз запущен из браузера. Можно приступать к анализу лога.

$ grep mail.php /tmp/apache2.strace
5345 read(9, "GET /mail.php HTTP/1.1\r\nHost:   12"..., 8000) = 397
5345 stat("/var/www/mail.php", {st_mode=S_IFREG|0644, st_size=256, ...}) = 0
5345 lstat("/var/www/mail.php", {st_mode=S_IFREG|0644, st_size=256, ...}) = 0
5345 open("/var/www/mail.php", O_RDONLY) = 10

Здесь в каждой строчке первое поле — PID, второе — вызов с параметрами, третье — значение, которое вернул вызов. В большинстве случаев, если возвращаемое значение не отрицательное — вызов отработал без ошибки. То есть, grep по mail.php не дал ничего интересного, кроме PID-процесса (5345), который его обрабатывал. Что ж, запустим grep по PID:

$ grep 5345 /tmp/apache2.strace
5340 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3bf2eada10) = 5345
5345 read(9, "GET /mail.php HTTP/1.1\r\nHost:   12"..., 8000) = 397
5345 stat("/var/www/mail.php", {st_mode=S_IFREG|0644, st_size=256, ...}) = 0
5345 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3bf2eada10) = 5347

Опять ничего интересного по поводу ошибки. Но на последней строчке с помощью системного вызова clone создается дочерний процесс с PID 5347.

Grep по 5347:

`$ grep 5347 /tmp/apache2.strace
5345 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3bf2eada10) = 5347
5347 execve("/bin/sh", ["sh", "-c", "/usr/sbin/sendmail -t -i "], [/* 6 vars */] = -1 EACCES   (Permission denied)`

Бинго! Для отправки почты используется /usr/sbin/sendmail, а вызов ругается на отсутствие прав. Причем права на sendmail выставлены корректно, а вот на /bin/sh — нет. Каким-то образом оказалось, что права на /bin/sh были 770 (при владельце и группе root), то есть пользователь www-data (от которого работал apache) не имел прав на выполнение. Корректировка прав исправила это недоразумение.

Проблемы с сетью

Иногда strace позволяет решать сетевые проблемы гораздо быстрее, чем tcpdump. В частности, с помощью strace очень удобно отслеживать, к каким сервисам и в каком порядке обращается приложение для определения имен.

Трассируем:

$ strace -f -e trace=network firefox xakep.ru
7879 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
7879 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = 0
7879 sendto(3, "\2\0\0\0\v\0\0\0\7\0\0\0passwd\0",   19, MSG_NOSIGNAL, NULL, 0) = 19

Вызов connect из листинга показывает, что Firefox сначала обращается к сервису NSCD (кэширующий демон) для разрешения имен, а только потом, если NSCD ничего не выдаст — к DNS. А если в NSCD завис старый адрес, то и ломиться он будет именно на него.

Проблемы с псевдоустройствами

Бывает, что какое-то приложение просто виснет, не выдавая никаких ошибок и завершаясь только по kill. Или работает, но тормозит на, казалось бы, простейшей операции. Приведу пример: есть старенький Debian Etch, на нем squid из репозитория с простой NCSA аутентификацией и SAMS для удобного управления. После создания пользователя через SAMS при релоаде squid долго тормозит на операциях добавления пользователей.

# strace -f -o /tmp/samsdaemon /etc/init.d/samsd start
15773 13:16:03 stat64("/etc/squid/ncsa.sams", {st_mode=S_IFREG|0644, st_size=314, ...}) = 0
15773 13:16:03 open("/etc/squid/ncsa.sams", O_RDONLY|O_APPEND|O_LARGEFILE) = 3   15773 13:16:03 close(3) = 0
15773 13:16:03 open("/dev/random", O_RDONLY) = 3

На последнем вызове система задумывается больше, чем на минуту. Значит, проблема в /dev/random. SAMS применяет его для создания хешей паролей пользователей. Самое простое решение — использовать /dev/urandom, который гораздо быстрее, чем /dev/random.

Для применения strace есть некоторые ограничения. Во-первых, понятно, что не следует использовать этот инструмент в рабочем окружении (трассировка apache на высоконагруженном productionсервере будет большой ошибкой) — производительность приложения в режиме трассировки сильно снижается. Второе ограничение — это возможные проблемы с трассировкой 32-битных приложений на 64-битной системе. И, наконец, третье — некоторые проги падают при выполнении трассировки вследствие наличия либо багов, либо защиты от трассировок (в основном это касается, конечно, проприетарного софта).