Когда программа отказывается стартовать, молча падает или виснет, первое, что хочется узнать, — какие системные вызовы она выполняет и на каком шаге всё ломается. Именно для этого существуют strace и ltrace: первый перехватывает обращения процесса к ядру, второй показывает вызовы функций из разделяемых библиотек. Оба инструмента входят в набор дистрибутива, не требуют пересборки приложения и за пару минут дают ответ там, где просмотр логов и тяжёлый отладчик выглядят избыточно.
Как strace работает под капотом
strace построен на механизме ptrace: трассировщик присоединяется к целевому процессу, и ядро останавливает его перед каждым системным вызовом, передавая управление наблюдателю. Приложение замедляется в десятки и сотни раз — это цена наблюдаемости. В вывод попадают имена вызовов, аргументы и результаты: openat, read, futex, clone, connect. Стандартная запись выглядит так: сначала вызов с аргументами, затем результат после знака равенства; у неудачных вызовов там код ошибки с расшифровкой.
Доступ к трассировке контролирует политика Yama. Значение 1 в файле /proc/sys/kernel/yama/ptrace_scope разрешает отладку только собственных дочерних процессов, поэтому подключение к чужому сервису требует root либо повышения уровня. Проверить состояние и назначить его можно так:
cat /proc/sys/kernel/yama/ptrace_scope
sudo sysctl -w kernel.yama.ptrace_scope=1
На рабочем сервере держите уровень 1 и запускайте трассировку под root: это ограничение защищает от чужого чтения памяти вашего процесса.
Установка и первый запуск
sudo apt install strace ltrace
strace -o /tmp/ls.trace ls /etc
head -20 /tmp/ls.trace
strace -c ls /tmp
Флаг -c заменяет подробный вывод сводной таблицей: сколько раз вызов встретился, сколько завершилось ошибкой и сколько времени он занял. Колонка errors отдельно показывает неудачные попытки — по ней сразу видно, ищет ли программа несуществующие файлы или дёргает сеть вхолостую. Это первый шаг, когда непонятно, куда смотреть, — цифры сразу указывают на подозрительные вызовы вроде массовых openat или connect к адресу, который никто не открывал.
Фильтры: смотреть только нужное
Полная трасса даже короткой команды уходит в тысячи строк, поэтому класс вызовов ограничивают явно:
strace -f -e trace=%file ./myapp
strace -e trace=network curl -s https://example.com
strace -e trace=process,desc ./myapp
Шаблон %file собирает все операции с файлами, network — сокеты, desc — файловые дескрипторы, process — fork и execve, memory — управление памятью. Несколько классов перечисляются через запятую, отрицание задаётся префиксом %. Спутники вывода расширяют картину: -s 200 удлиняет строки аргументов, -yy подписывает дескрипторы типами (socket, pipe, anon_inode), а метки времени и длительность вызова задают опции -tt и -T. Длинную трассу удобно сохранять в файл и искать по нему:
strace -s 200 -tt -yy -T -e trace=desc ./myapp > /tmp/trace.log 2>&1
grep -n 'EAGAIN' /tmp/trace.log | head
wc -l /tmp/trace.log
Разбор отказа в запуске
Самый частый сценарий — бинарник падает сразу после запуска или жалуется, что файла нет, хотя он на месте. Трассировка выдаёт цепочку попыток поиска:
strace -f -e trace=file ./myapp 2>&1 | grep -E 'ENOENT|EACCES|EPERM'
ldconfig -p | grep libfoo
Код ENOENT на открытии библиотеки означает, что загрузчик искал её в кэше /etc/ld.so.cache и не нашёл: добавьте каталог в конфиг из /etc/ld.so.conf.d и выполните ldconfig. Код EACCES указывает на права — реальный файл нечитаем от имени запускающего пользователя, что часто встречается у сервисов под системной учётной записью. Порядок открытых путей в выводе подсказывает, какой именно каталог приложение считает штатным.
Ловим падение сервиса при старте
Подключиться к работающему демону легко, а поймать момент падения при запуске сложнее: процесс живёт доли секунды и исчезает вместе с выводом. Надёжный приём — временно завернуть исполняемый файл в strace через переопределение юнита. Команда systemctl edit откроет drop-in в каталоге /etc/systemd/system/myservice.service.d/:
sudo systemctl edit myservice
[Service]
ExecStart=
ExecStart=/usr/bin/strace -f -tt -T -o /tmp/svc.trace /usr/sbin/myservice --config /etc/myservice/config.yml
sudo systemctl restart myservice
tail -50 /tmp/svc.trace
Пустая строка ExecStart= обязательна: без неё systemd откажется заменять команду из основного юнита. После разбора выполните systemctl revert myservice и перезапустите сервис — в трассе останется точная последовательность вызовов до места падения вместе с кодами ошибок.
Диагностика зависшего сервиса
Когда сервис не отвечает, но процесс жив, пригодится подключение к уже запущенному процессу:
pid=$(systemctl show --property MainPID --value nginx)
sudo strace -p "$pid" -f -tt -T
sudo strace -c -p "$pid"
Частая картина: потоки сидят в futex_wait_queue_me — ждут блокировку, которую никто не отдаёт; epoll_wait — ждут сетевые события, что нормально для сервера; read на дескрипторе — ждут данные от внешнего сервиса. Если строк нет несколько секунд подряд, процесс вообще не делает вызовов: смотрите стек через perf или gdb, а не strace.
ltrace: разговор с библиотеками
ltrace работает на уровне разделяемых библиотек: он разрешает символы через загрузчик и печатает вызовы printf, malloc, strlen, даже если они идут из libc. Для динамически слинкованных программ это короткий путь к пониманию логики:
ltrace -C -S ./myapp 2>&1 | head -50
ltrace -e 'malloc+free+strlen' ./myapp
ltrace -p 4321
Флаг -C включает деманглинг C++-имён, -S добавляет системные вызовы в тот же поток, -e перечисляет нужные функции через плюс. Ограничения жёсткие: статически слинкованный или полностью очищенный от символов бинарник ltrace не увидит, а собственные библиотеки без отладочной информации покажет лишь как адреса. В таких случаях помогает eBPF-трассировка или собственный перехват через LD_PRELOAD.
Практический чек-лист
- Начните со strace -c на процесс: сводка экономит десятки строк.
- Сузьте вывод фильтром %file или network, чтобы убрать шум.
- Добавляйте -f, если приложение порождает дочерние процессы и воркеры.
- Читайте коды ошибок: ENOENT, EACCES, EAGAIN — это и есть ответы.
- Для библиотек переходите к ltrace, для статической сборки — к eBPF.
- Помните об оверхеде: тяжёлые продакшен-нагрузки в трассировку не берут.
Итог
strace и ltrace — набор микроскопов для процесса: один показывает границу с ядром, второй — разговор с библиотеками. Работайте в правильном порядке: сводная статистика, фильтр по классу вызовов, разбор кодов ошибок и только потом детальный вывод. С таким порядком диагностика «не запускается» и «висит» превращается в пятиминутную задачу, а не в часы перебора гипотез.