$ strace -ff -o trace.log ./my_app --param=value
Системный вызов — фундаментальная концепция ядра Linux, обеспечивающая взаимодействие процессов с ОС. Когда stdout внезапно молчит, сокет завис, а процесс уходит в бесконечное ожидание, strace
становится вашим скальпелем для вскрытия. Этот инструмент трассирует все системные вызовы и сигналы целевого процесса, превращая чёрный ящик в прозрачную систему.
Зачем strace вместо дебаггера?
Разработчики часто недооценивают strace, предпочитая классические отладчики. Его сила — в работе непосредственно на границе пользовательского пространства и ядра. В отличие от gdb, strace показывает:
- Блокирующие операции (read, write, select)
- Сетевые взаимодействия (connect, accept)
- Доступ к файловой системе (open, stat)
- Управление памятью (brk, mmap)
- Сигналы (SIGSEGV, SIGPIPE)
Плюс:
- Для многих проблем достаточно статически слинкованного strace
- Не требует символов отладки (debug symbols)
- Шаблоны ошибок ядра видны сразу
Базовое применение: Сценарий "Процесс зависает"
Сталкивались с ситуацией, когда процесс запущен, но не реагирует? Физика процесса:
$ strace -p 14208
# Вывод:
# accept4(3, <unfinished ...>
# --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=1470, si_uid=1000} ---
# +++ killed by SIGTERM +++
Здесь видно: процесс блокирован на вызове accept4, ожидая входящих соединений. SIGTERM убивает его. Без strace вы гадали бы на кофейной гуще.
Опции для эффективного захвата
-
Трассировка потомка с первого шага
-f
форкается вместе с целевым процессом:bashstrace -f -o child.log make test
-
Фильтрация по системным вызовам
-e trace=network
или-e trace=file
:bashstrace -e trace=openat,connect python3 app.py
-
Сбор статистики времени
-T
показывает время выполнения каждого вызова:bashstrace -T ./calculate_fibonacci 1000 # openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 3 <0.000102>
Решение проблем производительности
Вызовы чаще всего замедляющие приложения:
-
Неожиданные системные вызовы
Наивная реализация может вызыватьgettimeofday()
в горячем цикле вместо часа таймера ядра. -
Блокирующие операции
Вызовwrite()
на медленный NFS может занимать секунды вstrace -T
:textwrite(1, "A", 1) = 1 <1.201543>
-
Системные прерывания
ЧастыеSIGPROF
от таймера реального времени:text--- SIGPROF {si_signo=SIGPROF, si_code=SI_KERNEL} ---
Статистика для анализа
-c
даёт сводку по вызовам:
$ strace -cf -p 18544
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
95.84 10.521274 4608 2282 epoll_wait
3.91 0.429061 187 2293 write
0.19 0.020519 8 2609 read
Здесь процесс проводит 95% времени в ожидании событий — верный признак хорошо написанного сервера.
Нетипичные сценарии
Сетевые притирки
Когда nginx не слушает порт:
$ strace -e trace=bind,listen nginx
...
bind(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("0.0.0.0")}, 16) = -1 EACCES (Permission denied)
Ответ: Запуск с непривилегированного порта без sudo
.
Почему "No space left on device" при свободном диске?
Трассируем в момент ошибки:
openat(AT_FDCWD, "/app/data.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0666) = -1 ENOSPC (No space left on device)
stat("/", {st_dev=makedev(0x17, 0x1), ...}) = 0
Решение:
$ df -i /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 65536 65536 0 100% /
Исчерпаны inodes на разделе — или вы создаёте миллионы мелких файлов, или node_modules
заняли всё.
Мультитрейдинг: Где мой мьютекс?
Проблема: Подозрение на взаимную блокировку (deadlock). Запускаем:
strace -ff -e trace=sync,clone,sigtimedwait ./threaded_app
Ищем бесконечное зацикливание вокруг:
...
futex(0x7f1eae03a9d0, FUTEX_WAIT_PRIVATE, 0, NULL) = -1 ETIMEDOUT (Connection timed out)
futex(0x7f1eae03a9d0, FUTEX_WAIT_PRIVATE, 0, NULL
...
Здесь FUTEX_WAIT_PRIVATE
без FUTEX_WAKE
— тревожный сигнал блокировки.
Интеграция с инструментами
Strace + GDB: Изучение сверчки
Некоторые ошибки требуют вмешательства debugger. Цепляем GDB к процессу уже под strace:
# В первом терминале:
strace -i -p $(pgrep -f my_server)
# Во втором:
gdb -p $(pgrep -f my_server) \
-ex 'b some_function' \
-ex 'c'
Флаг -i
показывает адрес инструкции для привязки точки останова.
Perf Scripting для маленьких узких мест
Когда бутылочное горлышко — частые мелкие вызовы:
perf trace -e '!sendto,recvfrom' ./network_tool
perf
добавляет стек вызовов к информации о вызовах.
Основные болевые точки чтения вывода
-
EEPIPE vs SIGPIPE
Пишем в закрытый сокет?write()
вернёт-1
сerrno=EPIPE
, но только еслиSIGPIPE
не игнорирован. Ищите отсутствие:csignal(SIGPIPE, SIG_IGN);
-
Искусство дескрипторов
Всеopen/close/socket
отображаются числом. Сопоставляйте:textopenat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY) = 5 read(5, ...) close(5)
-
Проклятие stdin
Неожиданные вызовыread(0, ...)
у серверных процессов часто связаны с ошибочным чтением из stdin вместо сокета.
Когда strace бессилен
- Внутри изоляции: Docker/Podman с
--cap-add=SYS_PTRACE
- Ядро блокирует tracer (
ptrace() = -1 EPERM
) - Пользовательские нативные библиотеки, оборачивающие вызовы
Используйте bpftrace
или perf syscall_tracer
для низкоуровневой трассировки.
Ментальные модели отладки
-
Отличайте заблокированные процессы от занятых
Длительныйnanosleep
— плановое ожидание, аfutex
с ожиданием — потенциальная блокировка. -
Ошибки не всегда исключения
read()
возвращает 0? Это не ошибка — EOF на другом конце сокета. -
Приоритетность трассировок
Ищите first_wait_large — первый долгий вызов часто корень проблемы.
Разделённость команды разработки и ОС — тщательно управляемый конфликт. Strace делает его границу видимой. Потратьте час на изучение незнакомого процесса через strace
, и вы будете гораздо ближе к пониманию работы всей системы в целом.