Мастерство отладки: Глубокое погружение в strace

bash
$ 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)
  • Шаблоны ошибок ядра видны сразу

Базовое применение: Сценарий "Процесс зависает"

Сталкивались с ситуацией, когда процесс запущен, но не реагирует? Физика процесса:

bash
$ 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 вы гадали бы на кофейной гуще.

Опции для эффективного захвата

  1. Трассировка потомка с первого шага
    -f форкается вместе с целевым процессом:

    bash
    strace -f -o child.log make test
    
  2. Фильтрация по системным вызовам
    -e trace=network или -e trace=file:

    bash
    strace -e trace=openat,connect python3 app.py
    
  3. Сбор статистики времени
    -T показывает время выполнения каждого вызова:

    bash
    strace -T ./calculate_fibonacci 1000
    # openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 3 <0.000102>
    

Решение проблем производительности

Вызовы чаще всего замедляющие приложения:

  1. Неожиданные системные вызовы
    Наивная реализация может вызывать gettimeofday() в горячем цикле вместо часа таймера ядра.

  2. Блокирующие операции
    Вызов write() на медленный NFS может занимать секунды в strace -T:

    text
    write(1, "A", 1)                  = 1 <1.201543>
    
  3. Системные прерывания
    Частые SIGPROF от таймера реального времени:

    text
    --- SIGPROF {si_signo=SIGPROF, si_code=SI_KERNEL} ---
    

Статистика для анализа

-c даёт сводку по вызовам:

bash
$ 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 не слушает порт:

bash
$ 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" при свободном диске?

Трассируем в момент ошибки:

c
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

Решение:

bash
$ df -i /
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 65536  65536     0  100% /

Исчерпаны inodes на разделе — или вы создаёте миллионы мелких файлов, или node_modules заняли всё.

Мультитрейдинг: Где мой мьютекс?

Проблема: Подозрение на взаимную блокировку (deadlock). Запускаем:

bash
strace -ff -e trace=sync,clone,sigtimedwait ./threaded_app

Ищем бесконечное зацикливание вокруг:

text
...
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:

bash
# В первом терминале:
strace -i -p $(pgrep -f my_server)

# Во втором:
gdb -p $(pgrep -f my_server) \
    -ex 'b some_function' \
    -ex 'c'

Флаг -i показывает адрес инструкции для привязки точки останова.

Perf Scripting для маленьких узких мест

Когда бутылочное горлышко — частые мелкие вызовы:

bash
perf trace -e '!sendto,recvfrom' ./network_tool

perf добавляет стек вызовов к информации о вызовах.

Основные болевые точки чтения вывода

  1. EEPIPE vs SIGPIPE
    Пишем в закрытый сокет? write() вернёт -1 с errno=EPIPE, но только если SIGPIPE не игнорирован. Ищите отсутствие:

    c
    signal(SIGPIPE, SIG_IGN);
    
  2. Искусство дескрипторов
    Все open/close/socket отображаются числом. Сопоставляйте:

    text
    openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY) = 5
    read(5, ...)
    close(5)
    
  3. Проклятие stdin
    Неожиданные вызовы read(0, ...) у серверных процессов часто связаны с ошибочным чтением из stdin вместо сокета.

Когда strace бессилен

  • Внутри изоляции: Docker/Podman с --cap-add=SYS_PTRACE
  • Ядро блокирует tracer (ptrace() = -1 EPERM)
  • Пользовательские нативные библиотеки, оборачивающие вызовы

Используйте bpftrace или perf syscall_tracer для низкоуровневой трассировки.

Ментальные модели отладки

  1. Отличайте заблокированные процессы от занятых
    Длительный nanosleep — плановое ожидание, а futex с ожиданием — потенциальная блокировка.

  2. Ошибки не всегда исключения
    read() возвращает 0? Это не ошибка — EOF на другом конце сокета.

  3. Приоритетность трассировок
    Ищите first_wait_large — первый долгий вызов часто корень проблемы.

Разделённость команды разработки и ОС — тщательно управляемый конфликт. Strace делает его границу видимой. Потратьте час на изучение незнакомого процесса через strace, и вы будете гораздо ближе к пониманию работы всей системы в целом.