В этом уроке разберём, почему программа висит на Linux и как это увидеть, как искать пропавший конфиг, как ловить медленные места через strace тайминги и сводку strace c. Всё на конкретных кейсах, с разбором вывода по полям. И отдельно поговорим, где в 2026 году strace уже стоит заменить на eBPF - чтобы не положить прод трассировкой.
Что вообще делает strace и как читать его строки
Программа сама почти ничего не умеет: открыть файл, прочитать байты из сокета, выделить память, дождаться события - всё это она просит у ядра через системные вызовы (syscalls). strace садится между программой и ядром через механизм ptrace и записывает каждый такой запрос: что попросили, с какими аргументами и что ядро ответило.
Аналогия простая. Представь, что программа - это посетитель за стеклом в банке, а ядро - операционист. Сам посетитель тебе ничего не расскажет, но ты слышишь каждую его реплику в окошко: "дайте файл /etc/app.conf", "прочитайте мне из сокета номер 7", "подождите, я жду блокировку". strace - это и есть стенограмма всех реплик в окошко.
Одна строка вывода читается так:
Код: Выделить всё
openat(AT_FDCWD, "/etc/app/config.yml", O_RDONLY) = -1 ENOENT (No such file or directory)- openat - имя системного вызова (тут: открыть файл). Современный glibc для открытия почти всегда зовёт именно openat, а не старый open - не ищи в выводе open, фильтруй по openat.
- в скобках - аргументы: AT_FDCWD значит "относительно текущего каталога", дальше путь и флаги (O_RDONLY - только чтение).
- = - и дальше результат. Если число >= 0 - это, как правило, файловый дескриптор или количество байт, всё хорошо. Если -1 - вызов завершился ошибкой.
- ENOENT - символьное имя ошибки (errno), а в скобках её расшифровка. Это и есть главное: вот по какому пути файл не нашёлся.
Ставится strace из штатного репозитория: sudo apt install strace (Debian/Ubuntu/Astra Linux), sudo dnf install strace (Fedora/RHEL/RED OS). На 2026 актуальна ветка strace 6.x; если версия совсем старая (5.x и ниже) - часть удобных флагов вроде -yy может вести себя беднее, обнови пакет. Чтобы прицепиться к чужому процессу, обычно нужен root, поэтому почти все боевые команды идут через sudo.

Кейс 1. Программа висит: смотрим, на каком syscall застряла
Это классика жанра и самый частый вопрос - почему программа висит linux и где она зависла. Процесс жив, в ps висит, CPU не ест, но ничего не делает. Прицепляемся к нему по PID:
Код: Выделить всё
sudo strace -p 12345Код: Выделить всё
strace: Process 12345 attached
futex(0x7f9c0a280a0c, FUTEX_WAIT_PRIVATE, 2, NULL- futex(..., FUTEX_WAIT...) - ждёт блокировку (мьютекс) от другого потока. Частый признак дедлока или того, что другой поток сам застрял и не отпускает лок. Один futex в покое - норма; тревога, когда он не сдвигается минутами.
- read(7, ...) или recvfrom(...) - ждёт данные, которые не приходят. Цифра 7 - это файловый дескриптор; данные ждут из файла, пайпа или сети. Чаще всего - повисший ответ от базы или внешнего API.
- poll(...), epoll_wait(...), select(...) - ждёт события сразу на нескольких дескрипторах. Для сетевого демона (nginx, postgres) это нормальное состояние покоя: он просто ждёт нового запроса. Тревога - только если запросы идут, а он на них не реагирует.
- connect(...) - ждёт, пока установится соединение. Если висит надолго - сетевой таймаут: хост недоступен, файрвол режет молча, не тот порт.
- nanosleep(...) / clock_nanosleep(...) - программа сама себя усыпила. Это не баг, а явный sleep в коде; смотри, не зациклилась ли логика повторов.
Код: Выделить всё
sudo strace -yy -p 12345
recvfrom(7<TCP:[10.0.0.5:54122->10.0.0.9:5432]>, ...Код: Выделить всё
sudo ls -l /proc/12345/fd/7
lrwx------ 1 app app 64 jun 15 12:30 /proc/12345/fd/7 -> socket:[849213]Кейс 2. Не находит файл или конфиг
Программа жалуется "config not found" или молча падает на старте, а ты уверен, что конфиг лежит на месте. Проблема в том, что она ищет его не там, где ты думаешь. Отфильтруем только открытия файлов через -e trace:
Код: Выделить всё
sudo strace -f -e trace=openat -p 12345Код: Выделить всё
openat(AT_FDCWD, "/etc/myapp/config.yml", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/local/etc/myapp/config.yml", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "./config.yml", O_RDONLY) = 3Полезная связка: чтобы поймать сразу и "файл не открылся", и "файл открыли, но путь странный", добавь -e trace=openat,stat,statx (так увидишь и проверки наличия), а флаг -y подпишет, какому файлу соответствует каждый дескриптор в последующих read/write.
Если процесс ещё не запущен, можно сразу трассировать запуск, без attach:
Код: Выделить всё
strace -f -e trace=openat myapp --start 2>strace.logСовременная альтернатива (2026). Если речь именно про "какие файлы открывает приложение", в проде вместо strace часто берут eBPF-инструмент opensnoop из пакетов bcc/bpftrace: он ловит все open/openat по всей системе с накладными расходами около процента и не тормозит цель. Запуск sudo opensnoop-bpfcc -n myapp покажет PID, имя процесса, fd, код ошибки (ERR) и путь - тот же ENOENT, но без риска просадить сервис.
Кейс 3. Программа тормозит: ищем, где утекает время
Программа работает, но медленно, а профайлера под рукой нет. strace отлично показывает, не залипает ли она на системных вызовах. Тут три флага, которые надо знать наизусть.
Сводка -c. Самое мощное для первого взгляда. Запускаем с -c, дожидаемся конца работы (или жмём Ctrl+C при attach) - и strace выдаёт таблицу: сколько времени и вызовов ушло на каждый syscall.
Код: Выделить всё
sudo strace -c -p 12345
^C
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
71.20 0.412233 4041 102 recvfrom
22.05 0.127700 319 400 write
4.10 0.023740 11 2100 18 read
2.65 0.015350 7 2090 futex
------ ----------- ----------- --------- --------- ----------------
100.00 0.579023 4692 18 total- % time - доля времени, ушедшая на этот вызов. Верхняя строка после сортировки - твой главный подозреваемый.
- seconds - суммарное время в этом syscall. Это время в ядре, не настенные часы.
- usecs/call - среднее время на один вызов в микросекундах.
- calls - сколько раз вызвали.
- errors - сколько раз вернулась ошибка.
- syscall - имя вызова.
Тайминги -T и -r. Когда сводка показала виновника, хочется увидеть конкретные медленные вызовы в потоке. Тут помогают strace тайминги:
- -T дописывает в конце каждой строки длительность вызова в угловых скобках: <0.000456> - это секунды (тут 0.45 мс).
- -r ставит в начале строки относительное время - сколько прошло с предыдущего syscall.
- -tt добавляет абсолютную метку времени с микросекундами - удобно сшивать с логами приложения.
Код: Выделить всё
sudo strace -T -p 12345
recvfrom(7, "HTTP/1.1 200 OK"..., 4096, 0, NULL, NULL) = 1480 <0.812043>
write(1, "done\n", 5) = 5 <0.000018>Кто вызвал этот syscall: stack trace без gdb
Частый затык: видишь, ЧТО зовётся, но не понимаешь, ОТКУДА в коде. Раньше тут лезли в gdb. Сейчас у strace есть флаг -k - он печатает стек вызовов после каждого syscall (нужна сборка с libunwind, в современных дистрибутивах она штатная):
Код: Выделить всё
sudo strace -k -e trace=connect -p 12345
connect(9, {sa_family=AF_INET, sin_port=htons(5432), ...}) = -1 EINPROGRESS
> /usr/lib/libpq.so.5(PQconnectPoll+0x1a) [0x2b1a]
> /usr/lib/myapp(db_connect+0x44) [0x9c44]
> /usr/lib/myapp(main+0x210) [0x1210]Типичные грабли и заблуждения
- Сам strace замедляет программу - и сильно. Это не мелочь: ptrace-трассировка для горячего по syscall кода замедляет цель примерно на два порядка (в бенчах geteuid strace медленнее eBPF около 100 раз). Поэтому абсолютные тайминги под strace не равны реальным; используй его, чтобы найти, ГДЕ тормозит, а не для точного бенчмарка. На нагруженном проде высокочастотный сервис под strace можно реально уронить по латентности - либо ограничивай через -e нужными вызовами и -DD (отдельный поток трассировщика), либо вообще бери eBPF.
- Забыл -f и не увидел половину. Многопоточные и форкающиеся программы (nginx, postgres, питон/гоу с воркерами) без -f показывают только главный поток или процесс. Нет ожидаемых строк - первым делом добавь -f.
- Ищешь вывод в stdout. strace пишет в stderr. Чтобы сохранить в файл, бери -o trace.log или перенаправляй через 2>, иначе grep по выводу ничего не найдёт.
- "Завис на read - значит баг в read". Нет. read просто ждёт данные, которых нет. Проблема не в нём, а в том, кто должен эти данные прислать. syscall - это симптом, а не причина.
- Не можешь прицепиться: "Operation not permitted". Часто это ptrace_scope. Проверь cat /proc/sys/kernel/yama/ptrace_scope; если там 1 или выше - цепляйся через sudo либо трассируй процесс от того же пользователя. В контейнере добавь возможность --cap-add=SYS_PTRACE, иначе attach не разрешат.
- strace молчит и ничего не выводит. Если процесс намертво залип в ядре (например, в неубиваемом состоянии D - непрерываемый сон на диске), новых syscall нет, и strace тоже молчит. Тогда смотри стек ядра напрямую: sudo cat /proc/PID/stack и cat /proc/PID/wchan - они покажут, в какой функции ядра застрял процесс (если там io_schedule или подобное - висим на дисковом или сетевом вводе-выводе внутри ядра, и strace тут бессилен).
strace в 2026 никуда не делся и остаётся инструментом номер один, когда надо детально, по-человечески читаемо разобрать ОДИН процесс: повисший сервис, кривой запуск, поиск конфига. Его сила - подробный вывод аргументов и errno.
Но для постоянного наблюдения и для прода стандарт сместился к eBPF. Готовые инструменты из пакетов bcc и bpftrace дают почти ту же информацию с накладными расходами около процента и без остановки цели:
- opensnoop - какие файлы открываются и с какими ошибками (замена strace -e trace=openat по всей системе).
- syscount - сводка по частоте syscall и по процессам (живая замена strace -c, безопасная под нагрузкой).
- execsnoop - кто что запускает, tcpconnect/tcplife - сетевые соединения и их время жизни.
Мини-лаба: повтори руками прямо сейчас
Пятиминутная практика, чтобы strace стал родным. Понадобится один терминал.
- Висим. Запусти sleep 100 &, запомни PID из вывода, потом strace -p PID. Увидишь зависший clock_nanosleep без закрытой скобки - вот так выглядит "процесс ждёт". Останови strace через Ctrl+C, sleep оставь.
- Ищем файл. Выполни strace -e trace=openat cat /etc/hostname. Найди строку, где cat успешно открывает /etc/hostname (результат - дескриптор 3). Теперь strace -e trace=openat cat /nope/none.txt - и поймай ENOENT на несуществующем пути.
- Подпись дескрипторов. Повтори первый шаг как strace -yy -e trace=openat,read cat /etc/hostname и убедись, что strace сам подписал путь рядом с fd. Это тот приём, что экономит ходьбу в /proc.
- Сводка по времени. Запусти strace -c ls -R /usr/share >/dev/null. В таблице найди, на какой syscall ушло больше всего вызовов и времени (скорее всего getdents64 или statx). Это и есть "узкое место" обхода каталогов.
- Тайминги. Повтори strace -T -c ls -R /usr/share >/dev/null - и сопоставь сводку с тем, что видишь по строкам.
- Ты прицепился к зависшему процессу и видишь незавершённую строку futex(..., FUTEX_WAIT...). Чего ждёт программа и с чего начнёшь искать причину?
- В выводе openat встретилась строка с -1 ENOENT. Что это значит и что конкретно она тебе говорит про конфиг?
- Какие шесть колонок в выводе strace -c и какая из них первой подскажет, где программа теряет время?
- strace -p PID прицепился, но не выводит вообще ничего, а процесс висит. Что происходит и чем смотреть дальше?
- Когда в 2026 ты возьмёшь strace, а когда opensnoop/syscount на eBPF, и почему?
strace показывает реальный диалог программы с ядром, и почти любой "висит/падает/тормозит" читается по нему за минуты. Висит - смотри незавершённый syscall (futex - лок, read/recvfrom - данные, connect - соединение), подпиши fd через -yy. Не находит файл - -e trace=openat и ищи ENOENT, вот по какому пути промахнулась. Медленно - сначала strace -c для общей картины, потом -T/-r, чтобы поймать конкретные долгие вызовы, и -k, чтобы понять, откуда в коде они растут. Три привычки: добавляй -f для многопоточных, помни про stderr, держи в голове, что syscall - это симптом, а причину ищи на другом конце дескриптора. И главное по 2026: strace - для разового разбора одного процесса, а на проде под нагрузкой бери eBPF (opensnoop, syscount), чтобы не уронить сервис самой диагностикой.