Перейти к содержанию

📖 Полное руководство по strace

strace — утилита для трассировки системных вызовов и сигналов в Linux. Незаменима при отладке, диагностике и изучении поведения программ без доступа к исходному коду.


📋 Содержание

  1. Что такое strace и как это работает
  2. Установка
  3. Синтаксис команды
  4. Чтение вывода strace
  5. Основные опции
  6. Фильтрация системных вызовов
  7. Работа со временем и статистикой
  8. Работа с файлами и дескрипторами
  9. Работа с процессами и потоками
  10. Продвинутые возможности
  11. Практические сценарии с разбором
  12. Быстрые рецепты
  13. Ограничения и альтернативы
  14. Частые системные вызовы

1. Что такое strace и как это работает

Зачем нужны системные вызовы

Программы в Linux работают в двух режимах:

  • User space (пространство пользователя) — здесь живёт ваш код, Python, nginx, bash
  • Kernel space (пространство ядра) — здесь ядро управляет железом, памятью, процессами

Программа не может напрямую открыть файл, отправить пакет или выделить память — она обязана попросить об этом ядро через строго определённый интерфейс — системные вызовы (syscalls).

┌─────────────────────────────────────┐
│         User Space                  │
│  ┌──────────┐    ┌────────────────┐ │
│  │  Ваш код │───►│  libc (glibc)  │ │
│  └──────────┘    └───────┬────────┘ │
│                          │          │
│              fopen() → open() syscall│
├──────────────────────────┼──────────┤
│         Kernel Space     ▼          │
│  ┌─────────────────────────────────┐│
│  │  Ядро Linux (sys_open)          ││
│  └──────────────┬──────────────────┘│
│                 ▼                   │
│  ┌──────────────────────────────┐   │
│  │  Драйверы, файловая система  │   │
│  └──────────────────────────────┘   │
└─────────────────────────────────────┘

Важный нюанс: функции из libc (например, fopen, printf, malloc) — это не системные вызовы. Это обёртки, которые внутри делают один или несколько реальных syscalls. strace показывает именно syscalls, поэтому имена в трассе могут отличаться от того, что вы вызвали в коде. Например, fopen() в трассе превратится в openat(), а printf() — в write().

Как strace перехватывает вызовы: механизм ptrace

strace использует системный вызов ptrace (process trace) — механизм ядра, позволяющий одному процессу наблюдать и контролировать другой.

Что происходит при запуске strace ./myapp:

1. strace вызывает fork() → создаётся дочерний процесс
2. Дочерний процесс вызывает ptrace(PTRACE_TRACEME)
   → сообщает ядру: "я хочу быть трассируемым"
3. Дочерний процесс вызывает execve("./myapp", ...)
   → запускается ваша программа
4. Ядро останавливает myapp ПЕРЕД каждым syscall
5. strace просыпается, читает регистры (имя вызова, аргументы)
6. strace говорит ядру: "продолжай" → ptrace(PTRACE_SYSCALL)
7. Ядро выполняет syscall, останавливает myapp ПОСЛЕ него
8. strace читает регистры снова (возвращаемое значение)
9. Цикл повторяется

Схема взаимодействия:

strace процесс              myapp процесс
     │                           │
     │    fork() + execve()      │
     ├──────────────────────────►│
     │                           │
     │◄──── SIGTRAP (syscall) ───┤  ← ядро останавливает myapp
     │                           │
     │  ptrace(PTRACE_GETREGS)   │
     ├──────────────────────────►│  ← читаем аргументы
     │◄──────────────────────────┤
     │                           │
     │  [strace печатает вызов]  │
     │                           │
     │  ptrace(PTRACE_SYSCALL)   │
     ├──────────────────────────►│  ← разрешаем продолжить
     │                           │
     │  [ядро выполняет syscall] │
     │                           │
     │◄──── SIGTRAP (return) ────┤  ← ядро останавливает снова
     │                           │
     │  ptrace(PTRACE_GETREGS)   │
     ├──────────────────────────►│  ← читаем результат
     │◄──────────────────────────┤
     │                           │
     │  [strace печатает = X]    │
     │                           │

⚠️ Почему strace замедляет программу в 10–100 раз: на каждый syscall происходит минимум 4 переключения контекста (user→kernel→strace→kernel→user). Каждое переключение стоит ~1–10 микросекунд. Если программа делает 100 000 вызовов в секунду — накладные расходы огромны.

Что strace показывает, а что нет

Видно в strace НЕ видно в strace
Системные вызовы (open, read, write...) Вызовы функций внутри библиотек
Аргументы и результаты каждого вызова Что происходит внутри ядра
Полученные сигналы Алгоритмы работы программы
Время выполнения каждого вызова Вызовы между потоками через разделяемую память
Какие файлы открыты, к каким портам подключается Данные в кеше CPU/памяти

2. Установка

Ubuntu / Debian

sudo apt install strace

RHEL / AlmaLinux / Fedora

sudo dnf install strace

Sailfish OS (через SSH)

# Подключиться к устройству
ssh defaultuser@<device_ip>

# Проверить доступность репозиториев
ssu domain
# Успешный вывод: Device domain is currently: sales

# Найти пакет
pkcon search strace

# Установить
devel-su pkcon install strace

Проверка установки

strace -V
# strace -- version 5.x

# Тест: трассировка простой команды
strace echo hello 2>&1 | head -5

3. Синтаксис команды

Основные формы запуска

# Форма 1: запустить программу под трассировкой
strace [опции] команда [аргументы команды]

# Форма 2: присоединиться к уже работающему процессу
strace [опции] -p PID

# Форма 3: только статистика (без построчного вывода)
strace -c [опции] команда [аргументы]

Разбор примера команды

strace -f -tt -T -y -e trace=%file -o /tmp/trace.log ./myapp --config /etc/app.conf
#       │   │   │  │  │              │                  │
#       │   │   │  │  │              │                  └── аргументы myapp
#       │   │   │  │  │              └── куда писать вывод (не в stderr)
#       │   │   │  │  └── фильтр: только файловые syscalls
#       │   │   │  └── показывать пути вместо номеров FD
#       │   │   └── показывать время каждого вызова
#       │   └── временная метка с микросекундами
#       └── следить за дочерними процессами

4. Чтение вывода strace

Анатомия одной строки

имя_вызова(аргумент1, аргумент2, ...) = результат <время>

Разберём подробно каждую часть:

openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 3 <0.000042>
  │       │           │               │               │      │
  │       │           │               │               │      └─ время выполнения (с -T)
  │       │           │               │               └─ возвращаемое значение (FD=3)
  │       │           │               └─ флаги открытия (побитовое ИЛИ)
  │       │           └─ путь к файлу
  │       └─ AT_FDCWD = "относительно текущей директории"
  └─ имя системного вызова

Полный разбор типичных строк

# ── ОТКРЫТИЕ ФАЙЛА ──────────────────────────────────────────────
openat(AT_FDCWD, "/etc/hosts", O_RDONLY) = 5
# Открыли /etc/hosts только для чтения.
# Ядро вернуло дескриптор 5. Теперь через FD=5 программа читает файл.

# ── ОШИБКА: ФАЙЛ НЕ НАЙДЕН ──────────────────────────────────────
openat(AT_FDCWD, "/etc/myapp.conf", O_RDONLY) = -1 ENOENT (No such file or directory)
# Попытка открыть конфиг — файла нет.
# -1 = ошибка. ENOENT = конкретный код ошибки. В скобках — человекочитаемое описание.

# ── ОШИБКА: НЕТ ПРАВ ────────────────────────────────────────────
openat(AT_FDCWD, "/etc/shadow", O_RDONLY) = -1 EACCES (Permission denied)
# Файл существует, но у процесса нет прав на чтение.

# ── ЧТЕНИЕ ДАННЫХ ───────────────────────────────────────────────
read(5, "root:x:0:0:root:/root:/bin/bash\n", 4096) = 32
# Читаем из FD=5 (наш /etc/hosts). Буфер 4096 байт.
# Реально прочитано 32 байта. Содержимое показано как строка.

# ── ЗАПИСЬ ДАННЫХ ───────────────────────────────────────────────
write(1, "Hello, World!\n", 14) = 14
# Пишем в FD=1 (stdout). 14 байт. Всё записано успешно.

# ── ЧАСТИЧНАЯ ЗАПИСЬ (проблема!) ────────────────────────────────
write(4, "bigdata...", 65536) = 32768
# Хотели записать 65536 байт, записали только 32768.
# Нормально для неблокирующих сокетов — нужно дописывать.

# ── ПОЛУЧЕНИЕ ИНФОРМАЦИИ О ФАЙЛЕ ────────────────────────────────
fstat(5, {st_mode=S_IFREG|0644, st_size=1234, st_uid=0, ...}) = 0
# fstat принимает FD=5, заполняет структуру stat.
# st_mode: обычный файл (S_IFREG), права 0644.
# = 0 означает успех (для большинства вызовов, не возвращающих данные).

# ── СЕТЕВОЕ СОЕДИНЕНИЕ ──────────────────────────────────────────
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("93.184.216.34")}, 16) = 0
# Подключаемся к 93.184.216.34:80 (HTTP).
# AF_INET = IPv4. htons(80) = порт 80 в сетевом порядке байт.
# = 0 означает успешное подключение.

# ── ОТКАЗ В ПОДКЛЮЧЕНИИ ─────────────────────────────────────────
connect(3, {sa_family=AF_INET, sin_port=htons(5432), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED
# Попытка подключиться к PostgreSQL на localhost:5432.
# ECONNREFUSED = никто не слушает этот порт (или firewall).

# ── НЕЗАВЕРШЁННЫЙ ВЫЗОВ (многопоточность) ───────────────────────
read(7, <unfinished ...>
# Поток заблокирован на чтении. Другой поток в это время делает свои вызовы.

# ── ВОЗОБНОВЛЕНИЕ ВЫЗОВА ────────────────────────────────────────
<... read resumed> "HTTP/1.1 200 OK\r\n", 4096) = 17
# Тот же read вернул данные. strace склеивает начало и конец.

# ── ПОЛУЧЕНИЕ СИГНАЛА ───────────────────────────────────────────
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=1234, si_uid=1000} ---
# Процесс получил SIGTERM от процесса 1234 (пользователь 1000).

# ── ЗАВЕРШЕНИЕ ПРОЦЕССА ─────────────────────────────────────────
+++ exited with 0 +++      # нормальное завершение
+++ exited with 1 +++      # завершение с ошибкой
+++ killed by SIGKILL +++  # убит сигналом

Декодирование аргументов

strace автоматически декодирует числовые значения в человекочитаемый вид:

# Флаги открытия файла (побитовое ИЛИ числовых констант)
open("file", O_WRONLY|O_APPEND|O_CREAT, 0666)
#            └──────┘ └───────┘ └─────┘  └──┘
#           только    добавить  создать  права в
#           запись    в конец   если нет восьмеричном

# Права доступа к файлу
lstat("/dev/null", {st_mode=S_IFCHR|0666, st_rdev=makedev(1,3), ...}) = 0
#                             └──────┘└──┘
#                          тип файла  права
#                          (символьное устройство)

# Адрес сокета
bind(3, {sa_family=AF_INET6, sin6_port=htons(8080), sin6_addr=::}, 28)
#        └──────────────────────────────────────────────────────────┘
#                  вся структура sockaddr_in6 декодирована

Коды возврата

# Успех — зависит от вызова:
= 0       # успех без данных (chmod, mkdir, close...)
= 3       # успех, вернул дескриптор файла (open, socket...)
= 1024    # успех, вернул количество байт (read, write...)

# Ошибка — всегда -1 + код ошибки:
= -1 ENOENT (No such file or directory)  # файл не найден
= -1 EACCES (Permission denied)          # нет прав
= -1 ECONNREFUSED (Connection refused)   # порт закрыт
= -1 ETIMEDOUT (Connection timed out)    # таймаут
= -1 EADDRINUSE (Address already in use) # порт занят
= -1 EBADF (Bad file descriptor)         # неверный FD
= -1 EAGAIN (Resource temporarily unavailable) # повторить позже
= -1 ENOSPC (No space left on device)   # диск полон
= -1 EPERM (Operation not permitted)     # операция запрещена
= -1 ENOSYS (Function not implemented)   # вызов не существует

5. Основные опции

Группа: куда писать вывод

# По умолчанию strace пишет в stderr
strace ./app 2>trace.txt   # можно перенаправить stderr вручную

# Лучше использовать -o: быстрее и не смешивает с ошибками программы
strace -o trace.log ./app

# -o с '|' передаёт вывод в другую программу
strace -o '|grep ENOENT' ./app

# -A открывает файл в режиме добавления (полезно при повторных запусках)
strace -A -o trace.log ./app

Почему -o лучше перенаправления: Перенаправление 2>file работает, но strace пишет в stderr посимвольно — это медленно при большом потоке. Опция -o использует внутренний буфер и работает значительно быстрее.

Группа: управление трассировкой

# -f: следить за дочерними процессами и потоками
# Без -f вы видите только вызовы главного процесса!
strace -f ./app

# Как работает -f:
# При вызове fork()/clone()/vfork() strace автоматически
# начинает трассировать новый процесс/поток.
# Каждая строка помечается PID:
# 1234 write(1, "parent\n", 7) = 7
# 1235 write(1, "child\n", 6)  = 6

# -ff: как -f, но каждый процесс пишет в отдельный файл
strace -ff -o trace ./app
# Создаст: trace.1234 trace.1235 trace.1236 ...
# Удобно анализировать каждый процесс отдельно.

# Объединить файлы обратно с сортировкой по времени:
strace-log-merge trace | less

Группа: подавление лишнего вывода

# -q: убрать сообщения о присоединении/отсоединении
# Обычно strace пишет: "strace: Process 1234 attached"
strace -q -p 1234

# -qq: дополнительно убрать сообщения о завершении
# Обычно strace пишет: "+++ exited with 0 +++"
strace -qq ./app

Группа: дополнительная информация

# -v: полный вывод структур (без сокращений ...)
# Обычно:   fstat(3, {st_mode=S_IFREG|0644, st_size=1234, ...}) = 0
# С -v:     fstat(3, {st_mode=S_IFREG|0644, st_size=1234,
#                     st_uid=0, st_gid=0, st_atime=..., ...}) = 0
strace -v ./app

# -x: non-ASCII строки в hex (полезно для бинарных протоколов)
strace -x ./app
# Вместо: read(3, "data\x01\x02\x03", 7)
# Будет:  read(3, "\x64\x61\x74\x61\x01\x02\x03", 7)

# -xx: все строки в hex
strace -xx ./app

# -i: показывать адрес инструкции (для низкоуровневой отладки)
strace -i ./app
# [00007f3d4a2b1234] write(1, "hello\n", 6) = 6

6. Фильтрация системных вызовов

Без фильтрации типичная программа генерирует сотни строк только при старте (загрузка библиотек, настройка окружения). Фильтрация — ключевой навык эффективной работы со strace.

Базовый синтаксис -e trace=

# Конкретные вызовы (через запятую, без пробелов)
strace -e trace=open,read,write ./app

# Группа вызовов (начинается с %)
strace -e trace=%file ./app

# Исключить вызовы (! нужно экранировать в bash)
strace -e trace=\!brk,mmap,mprotect ./app
# или в кавычках:
strace -e 'trace=!brk,mmap,mprotect' ./app

# Комбинация групп
strace -e trace=%file,%network ./app

Группы системных вызовов с объяснением

# %file — всё что касается файлов и путей
strace -e trace=%file ./app
# Включает: open, openat, stat, lstat, access, chmod, chown,
#           rename, unlink, mkdir, rmdir, symlink, readlink, ...
# Когда использовать: программа не находит файл/конфиг,
#                     ошибки прав доступа, что читает при старте

# %network — сетевые операции
strace -e trace=%network ./app
# Включает: socket, connect, bind, listen, accept, send,
#           sendto, recv, recvfrom, getsockopt, setsockopt, ...
# Когда использовать: не может подключиться, DNS-проблемы,
#                     зависает при отправке/получении данных

# %process — управление процессами
strace -e trace=%process ./app
# Включает: fork, clone, execve, execveat, exit, exit_group,
#           wait4, waitpid, getpid, setpgid, ...
# Когда использовать: что запускает программа, как создаёт потоки

# %signal — работа с сигналами
strace -e trace=%signal ./app
# Включает: sigaction, sigprocmask, kill, tkill, sigpending, ...
# Когда использовать: программа не реагирует на сигналы

# %ipc — межпроцессное взаимодействие
strace -e trace=%ipc ./app
# Включает: msgget, msgsnd, msgrcv, shmget, shmat, semget, ...
# Когда использовать: проблемы с разделяемой памятью, очередями

# %desc — операции с дескрипторами
strace -e trace=%desc ./app
# Включает: read, write, close, dup, dup2, select, poll,
#           epoll_wait, fcntl, ioctl, ...
# Когда использовать: утечки дескрипторов, проблемы с I/O

# %memory — управление памятью
strace -e trace=%memory ./app
# Включает: mmap, munmap, mprotect, madvise, brk, ...
# Когда использовать: утечки памяти, ошибки сегментации

# %creds — учётные данные
strace -e trace=%creds ./app
# Включает: getuid, setuid, getgid, setgid, capget, capset, ...
# Когда использовать: проблемы с setuid, правами, capabilities

Фильтрация по статусу вызова

# Только вызовы с ошибкой (самый полезный фильтр для диагностики!)
strace -Z ./app
# или эквивалентно:
strace -e status=failed ./app

# Только успешные вызовы
strace -e status=successful ./app

# Только незавершённые (показывает что заблокировано прямо сейчас)
strace -e status=unfinished ./app

# Комбинации
strace -e status=failed,unfinished ./app

Пример: быстрая диагностика проблем:

# Вместо просмотра тысяч строк — только ошибки
strace -Z -y -e trace=%file ./broken_app

# Вывод покажет только:
# openat(AT_FDCWD, "/etc/app/config.yaml", O_RDONLY) = -1 ENOENT
# Сразу видно: нет конфига!

Фильтрация по пути к файлу

# Трассировать только вызовы, касающиеся конкретного пути
strace -P /etc/nginx/nginx.conf ./nginx
# Вывод:
# openat(AT_FDCWD, "/etc/nginx/nginx.conf", O_RDONLY) = 3
# read(3</etc/nginx/nginx.conf>, "worker_processes 4;\n...", 4096) = 512
# close(3</etc/nginx/nginx.conf>) = 0

# Несколько путей (фильтры объединяются по ИЛИ)
strace -P /etc -P /var/log ./app

# Комбинация с -y: покажет пути для всех FD
strace -y -P /var/log/app.log ./app

Фильтрация по файловым дескрипторам

# Только вызовы для конкретных FD
strace -e trace-fds=3,4,5 ./app

# Полезный сценарий: узнать FD нужного файла через -y, потом фильтровать
strace -y ./app 2>&1 | grep "myfile.log"
# write(7</var/log/myfile.log>, ...) = 128
# Теперь знаем FD=7 и можем:
strace -e trace-fds=7 ./app

7. Работа со временем и статистикой

Временны́е метки — выбор формата

# -t: время в формате HH:MM:SS (точность до секунды)
strace -t ./app
# 14:32:01 execve("./app", ...) = 0
# 14:32:01 openat(AT_FDCWD, "/etc/ld.so.cache", ...) = 3

# -tt: время с микросекундами (рекомендуется для диагностики)
strace -tt ./app
# 14:32:01.123456 execve("./app", ...) = 0
# 14:32:01.123891 openat(...) = 3
# Видна пауза в 435 микросекунд между вызовами!

# -ttt: Unix timestamp + микросекунды (для машинной обработки)
strace -ttt ./app
# 1697123461.123456 execve("./app", ...) = 0
# Удобно для diff-а и парсинга логов

# -r: относительное время с момента предыдущего вызова
strace -r ./app
#      0.000000 execve("./app", ...) = 0
#      0.000435 openat(...) = 3   ← 435мкс после предыдущего
#      0.000012 fstat(3, ...) = 0

Время выполнения каждого вызова: -T

# -T: показывает время внутри каждого системного вызова
strace -T ./app
# write(1, "hello\n", 6) = 6 <0.000043>
#                              └──────┘
#                         43 микросекунды потрачено на этот вызов

# Комбинация -tt и -T (самая информативная для диагностики зависаний)
strace -tt -T ./app
# 14:32:01.100000 connect(3, {sa_family=AF_INET, ...}, 16) = 0 <2.341567>
# ^^^^^^^^^^^^^^^^                                              ^^^^^^^^^
# начало вызова                                         2.3 секунды ждали!

Практический пример — найти медленный вызов:

strace -tt -T -o /tmp/trace.log ./slow_app

# Найти самые долгие вызовы
grep -oP '<\K[0-9.]+(?=>)' /tmp/trace.log | sort -n | tail -10
# 0.234567
# 0.891234
# 2.341567   ← подозрительно долго
# 5.000123   ← очень долго, скорее всего таймаут

Сбор статистики: -c и -C

# -c: только итоговая таблица (обычный вывод подавлен)
strace -c ./app
# Пока app работает — тишина. После завершения:
# % time     seconds  usecs/call     calls    errors syscall
# ------ ----------- ----------- --------- --------- --------
#  45.23    0.002341         234        10         2 read
#  28.15    0.001458          48        30           mmap
#  15.67    0.000811         811         1           execve
#   8.12    0.000420          42        10         5 openat
#   2.83    0.000146          14        10           fstat
# ------ ----------- ----------- --------- --------- --------
# 100.00    0.005176                    61         7 total

# -C: статистика + обычный построчный вывод (оба сразу)
strace -C ./app

Разбор таблицы статистики:

% time     seconds  usecs/call     calls    errors syscall
─────────────────────────────────────────────────────────
  45.23    0.002341         234        10         2 read
  │           │              │          │          │   │
  │           │              │          │          │   └── имя вызова
  │           │              │          │          └────── кол-во вызовов с ошибкой
  │           │              │          └───────────────── всего вызовов
  │           │              └──────────────────────────── среднее время (мкс)
  │           └─────────────────────────────────────────── суммарное системное время (сек)
  └─────────────────────────────────────────────────────── доля от всего времени (%)

Важно: seconds — это системное время (CPU time в режиме ядра), а не реальное время (wall time). Долгий read из сети может занимать секунды по wall time, но почти 0 по system time (CPU просто ждал данные).

Сортировка статистики

strace -c -S time ./app    # по суммарному времени (по умолчанию)
#               ────
#               что сжирает больше всего CPU в ядре

strace -c -S calls ./app   # по количеству вызовов
#               ─────
#               что вызывается чаще всего

strace -c -S errors ./app  # по количеству ошибок
#               ──────
#               что чаще всего падает

strace -c -S name ./app    # по алфавиту

8. Работа с файлами и дескрипторами

Декодирование дескрипторов: -y и -yy

Без -y — видим только номер:

read(7, "HTTP/1.1 200 OK\r\n", 4096) = 17
# Что такое FD=7? Непонятно.

С -y — видим путь или тип:

read(7</var/log/app.log>, "2024-01-01 error\n", 4096) = 17
read(8<TCP:[127.0.0.1:8080->192.168.1.1:54321]>, "GET / HTTP/1.1\r\n", 4096) = 15
read(9<pipe:[12345]>, "data from pipe", 4096) = 14
#    └────────────────────────────────┘
#       что скрыто за этим FD

С -yy — дополнительно информация о протоколах:

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3<TCP:[127.0.0.1]>
bind(3<TCP:[127.0.0.1]>, {sa_family=AF_INET, sin_port=htons(8080), ...}, 16) = 0
accept(3<TCP:[127.0.0.1:8080]>, ...) = 5<TCP:[127.0.0.1:8080->192.168.1.5:43210]>

Стандартные дескрипторы

FD=0 → stdin  (клавиатура или pipe)
FD=1 → stdout (экран или перенаправление)
FD=2 → stderr (экран или перенаправление)
FD=3+ → открываемые программой файлы, сокеты, pipe-ы

Дамп передаваемых данных

# --read=FD: показать содержимое всего, что читается из этого FD
strace --read=5 ./app
# read(5</etc/passwd>, "root:x:0:0:root:/root:/bin/bash\nbin:x:1:1:...", 4096) = 1024
# | 00000000  72 6f 6f 74 3a 78 3a 30  3a 30 3a 72 6f 6f 74  root:x:0:0:root |
# | 00000010  3a 2f 72 6f 6f 74 3a 2f  62 69 6e 2f 62 61 73  :/root:/bin/bas |
#   ^^^^^^^^   hex дамп                                        ASCII           

# --write=FD: показать что записывается
strace --write=1 ./app
# Видим всё, что программа пишет в stdout в hex-дампе

# Несколько FD сразу
strace --read=3,5 --write=4 ./app

⚠️ Безопасность: hex-дамп может содержать пароли, API-ключи, токены, cookies, содержимое файлов. Не передавайте такие логи без очистки чувствительных данных.

Управление длиной строк: -s

# По умолчанию строки обрезаются до 32 символов:
read(3, "GET /api/v1/users HTTP/1.1\r\nHost"..., 4096) = 1234
#                                              ^^^
#                                         обрезано здесь

# Увеличить до 256 символов:
strace -s 256 ./app
read(3, "GET /api/v1/users HTTP/1.1\r\nHost: example.com\r\nUser-Agent: curl/7.68\r\n...", 4096) = 1234

# Практически неограниченно:
strace -s 65535 ./app

# Примечание: имена файлов ВСЕГДА печатаются полностью,
# независимо от -s. Ограничение применяется только к
# содержимому буферов read/write.

9. Работа с процессами и потоками

Как Linux создаёт процессы и потоки

Понимание этого помогает правильно трактовать вывод strace:

fork()   → clone(SIGCHLD) → новый процесс, своя память
vfork()  → clone(VFORK)   → новый процесс, разделяет память до exec
thread   → clone(CLONE_VM|CLONE_FS|CLONE_FILES|...) → поток, общая память

В трассе strace:
# Новый ПРОЦЕСС (fork):
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|SIGCHLD, ...) = 5678
#                              └───────────────────────────────────────┘
#                              нет флагов разделения памяти → процесс

# Новый ПОТОК (pthread_create):
clone(child_stack=0x7f..., flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|...) = 5679
#                                 └────────────────────────────────────────────────────────────┘
#                                 флаги разделения ресурсов → поток

Флаг -f: почему он почти всегда нужен

# БЕЗ -f: видим только главный процесс
strace ./app
# 1234 execve("./app", ...) = 0
# 1234 clone(...) = 1235   ← создали дочерний процесс
# [1235 полностью невидим!]
# 1234 wait4(1235, ...) = 1235

# С -f: видим всё дерево
strace -f ./app
# 1234 execve("./app", ...) = 0
# 1234 clone(...) = 1235
# 1235 execve("/bin/helper", ...) = 0   ← дочерний виден!
# 1235 open("/etc/config", ...) = 3
# 1234 wait4(1235, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], ...) = 1235

Раздельные файлы для каждого процесса: -ff

strace -ff -o /tmp/trace ./app

# Создаст файлы:
# /tmp/trace.1234   ← главный процесс
# /tmp/trace.1235   ← дочерний процесс 1
# /tmp/trace.1236   ← дочерний процесс 2

# Анализировать один:
less /tmp/trace.1235

# Объединить с сортировкой по времени:
strace-log-merge /tmp/trace > /tmp/trace.merged
# Каждая строка получает временную метку из -tt

Присоединение к запущенному процессу: -p

# Простое присоединение
strace -p 1234

# Что происходит при attach:
# 1. strace посылает SIGSTOP процессу 1234
# 2. Ядро останавливает процесс
# 3. strace вызывает ptrace(PTRACE_ATTACH, 1234)
# 4. Начинается трассировка
# strace пишет: "strace: Process 1234 attached"

# Что происходит при detach (Ctrl+C):
# strace вызывает ptrace(PTRACE_DETACH, 1234)
# Процесс продолжает работу как ни в чём не бывало
# strace пишет: "strace: Process 1234 detached"

# Присоединение к нескольким процессам
strace -p 1234 -p 5678 -p 9012

# Присоединение ко всем потокам процесса (PID + TIDs)
strace -f -p $(pgrep myapp | head -1)

# Все процессы nginx одной командой:
strace -f $(pidof nginx | sed 's/\([0-9]*\)/-p \1/g')

⚠️ Возможный побочный эффект при attach: в редких случаях SIGSTOP может прервать нерестартуемый системный вызов в трассируемом процессе, что может вызвать EINTR. Учитывайте это на высоконагруженных системах.

Проверка политики ptrace (если attach запрещён)

cat /proc/sys/kernel/yama/ptrace_scope
# 0 = любой процесс может трассировать любой свой
# 1 = только родительский процесс (по умолчанию в Ubuntu)
# 2 = только root
# 3 = запрещено всем

# Если получаете "Operation not permitted":
sudo strace -p 1234   # или запустить через sudo

10. Продвинутые возможности

Трассировка стека вызовов: -k

Показывает цепочку вызовов функций, которая привела к системному вызову:

strace -k -e trace=write ./app
write(1, "Hello\n", 6) = 6
 > /lib/x86_64-linux-gnu/libc.so.6(__write+0x14) [0x110154]
 > /home/user/app(do_write+0x50) [0x78a]      ← наша функция do_write
 > /home/user/app(process_request+0x120) [0x8f2]
 > /home/user/app(main+0x14) [0x7d1]
 > /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x21b97]

Как это работает: strace использует libunwind или анализ стека через ptrace для восстановления цепочки вызовов в момент syscall.

Требования для читаемого стека:

# 1. Скомпилировать с отладочными символами:
gcc -g -o myapp myapp.c

# 2. Установить отладочные символы для библиотек (Debian/Ubuntu):
sudo apt install libc6-dbg

# 3. Для Python-расширений нужен python3-dbg или python3.x-dev

⚠️ Предупреждение: -k резко увеличивает объём вывода и замедляет трассировку ещё сильнее. Используйте только с узким фильтром -e trace=.

Инъекция ошибок и задержек: -e inject

Это мощнейшая функция strace для тестирования устойчивости кода к ошибкам — без изменения кода программы.

Как работает инъекция

Обычное выполнение:
  Программа → syscall → Ядро выполняет → возвращает результат

С инъекцией ошибки:
  Программа → syscall → strace перехватывает → возвращает фиктивную ошибку
                                                (ядро syscall НЕ выполняет)

С инъекцией задержки:
  Программа → syscall → strace ждёт N мс → Ядро выполняет → возвращает результат

Синтаксис

-e inject=ВЫЗОВ:ПАРАМЕТР1=ЗНАЧЕНИЕ1:ПАРАМЕТР2=ЗНАЧЕНИЕ2

Параметры инъекции

Параметр Значение Пример
error=ERRNO Вернуть эту ошибку вместо выполнения error=ENOENT
retval=N Вернуть это значение (имитация успеха) retval=0
signal=SIG Доставить сигнал при вызове signal=SIGSEGV
delay_enter=T Задержка перед вызовом delay_enter=500ms
delay_exit=T Задержка после вызова delay_exit=1s
when=EXPR На каком вызове применить when=2, when=3+, when=1+2

Параметр when — подробно

when=1      # только первый вызов
when=2      # только второй вызов
when=3      # только третий вызов

when=1+     # начиная с первого (= все вызовы)
when=3+     # начиная с третьего (первые два — нормально)

when=1+2    # 1-й, 3-й, 5-й, 7-й... (каждый второй начиная с 1)
when=2+3    # 2-й, 5-й, 8-й, 11-й... (каждый третий начиная с 2)

Примеры инъекций с объяснениями

# ── ИНЪЕКЦИЯ ОШИБКИ ─────────────────────────────────────────────

# Все попытки открыть файл получат "файл не найден"
strace -e inject=openat:error=ENOENT ./app
# openat(AT_FDCWD, "/etc/config", O_RDONLY) = -1 ENOENT (INJECTED)
# Проверяем: как app обрабатывает отсутствие конфига?

# Только первое подключение получит "отказано"
strace -e inject=connect:error=ECONNREFUSED:when=1 ./app
# Первый connect → ECONNREFUSED. Остальные — нормально.
# Проверяем: есть ли механизм retry?

# Каждый второй read вернёт ошибку
strace -e inject=read:error=EIO:when=1+2 ./app
# Проверяем: устойчив ли код к случайным I/O ошибкам?

# Только запись в конкретный файл получит "нет места"
strace -y -P /var/log/app.log -e inject=file:error=ENOSPC ./app
# Проверяем: что происходит при полном диске?

# ── ИНЪЕКЦИЯ ЗАДЕРЖЕК ───────────────────────────────────────────

# Каждый connect занимает 2 секунды (имитация медленной сети)
strace -e inject=connect:delay_enter=2s ./app
# Проверяем: работает ли таймаут? Не зависает ли UI?

# Каждый read занимает 100ms (медленный диск)
strace -e inject=read:delay_exit=100ms:when=1+ ./app

# DNS медленно отвечает (задержка перед sendto на порт 53)
strace -e inject=sendto:delay_enter=500ms ./app

# ── КОМБИНАЦИИ ──────────────────────────────────────────────────

# Первые 3 connect нормальные, начиная с 4-го — таймаут
strace -e inject=connect:error=ETIMEDOUT:when=4+ ./app

# Второй вызов write в /tmp/output возвращает EIO
strace -y -P /tmp/output -e inject=file:error=EIO:when=2 ./app

# ── КРАТКАЯ ФОРМА (fault) ───────────────────────────────────────
# -e fault=ВЫЗОВ:when=N — возвращает ENOSYS без указания ошибки
strace -e fault=write:when=1 ./app
# write(1, "hello", 5) = -1 ENOSYS (INJECTED)

Ограничение числа вызовов

# Остановить трассировку после N системных вызовов
# Полезно для захвата только начала работы программы
strace --syscall-limit=50 ./app

# Комбинация: первые 100 файловых вызовов
strace -e trace=%file --syscall-limit=100 ./app

11. Практические сценарии с разбором

Сценарий 1: Программа не запускается — нет файла или прав

Симптом: ./myapp выдаёт непонятную ошибку или молча падает.

# Шаг 1: запуск с отображением только ошибок
strace -f -Z -y -e trace=%file ./myapp 2>&1

# Типичный вывод:
# openat(AT_FDCWD, "/etc/myapp/config.yaml", O_RDONLY) = -1 ENOENT
# → Нет конфига! Создаём или указываем путь.

# openat(AT_FDCWD, "/usr/lib/myapp/plugin.so", O_RDONLY) = -1 ENOENT
# → Не найдена библиотека/плагин.

# execve("/usr/libexec/helper", [...]) = -1 EACCES
# → Нет прав на выполнение вспомогательного бинаря.
# Проверяем: ls -la /usr/libexec/helper

Реальный кейс — PHP не отправляет почту:

strace -f -o /tmp/php.log ./php mail.php

grep -i "eacces\|enoent\|execve" /tmp/php.log

# Находим:
# 5347 execve("/usr/sbin/sendmail", ["sendmail", "-t", "-i"], ...) = -1 EACCES
# → Права на /bin/sh или sendmail неверные!
ls -la /bin/sh /usr/sbin/sendmail
chmod 755 /bin/sh   # исправляем

Сценарий 2: Отладка сетевых проблем

Симптом: приложение не может подключиться, или подключается не туда.

# Полная трассировка сети с декодированием и временем
strace -f -tt -T -yy -e trace=%network -o /tmp/net.log ./app

# Анализ лога:
# 1. Смотрим куда подключается:
grep "connect(" /tmp/net.log

# Типичные находки:

# Отказ в подключении (порт закрыт):
connect(3, {sa_family=AF_INET, sin_port=htons(5432), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED <0.000123>
# → PostgreSQL не запущен! Проверяем: systemctl status postgresql

# Таймаут (firewall или нет маршрута):
connect(3, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("10.0.0.5")}, 16) = -1 ETIMEDOUT <30.000000>
# → 30 секунд ждал! Firewall блокирует Redis на 10.0.0.5:6379

# DNS кешируется в NSCD (обращение к unix-сокету):
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = 0
# → Программа использует кеш DNS, а не резолвер напрямую.
# Если DNS изменился — нужно сбросить NSCD кеш или перезапустить его.

# Чтение /etc/resolv.conf при каждом DNS-запросе (накладные расходы):
openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 5   # каждый раз!

Сценарий 3: Поиск блокирующего вызова в зависшем процессе

Симптом: процесс работает (не в zombie), но ничего не делает.

# Шаг 1: найти PID
pgrep -a myapp
# 1234 myapp --daemon

# Шаг 2: присоединиться
strace -p 1234 -f -tt -T -o /tmp/hang.log

# Подождать 5-10 секунд, затем Ctrl+C

# Шаг 3: посмотреть последние строки лога
tail -30 /tmp/hang.log

Интерпретация находок:

# Ждёт данные из сети (нормально для сервера в idle):
recvfrom(7<TCP:[...]>, <unfinished ...>
# Это нормально — сервер ждёт клиентов. Не зависание.

# Заблокирован на мьютексе (возможный deadlock):
futex(0x7f3b2a4b1234, FUTEX_WAIT, 2, NULL) = ? ERESTARTSYS
# FUTEX_WAIT = ждёт освобождения mutex.
# Если висит долго → deadlock или поток держит mutex, но завис сам.
# Проверяем: strace -f всего приложения, ищем кто держит этот адрес.

# Спит в nanosleep (нормально, если это не слишком долго):
nanosleep({tv_sec=300, tv_nsec=0}, <unfinished ...>
# Ждёт 300 секунд (5 минут). Может быть нормальным поведением.

# Заблокирован на чтении из pipe (ждёт данных от другого процесса):
read(6</proc/1235/fd/1 (pipe:[12345])>, <unfinished ...>
# Дочерний процесс 1235 не пишет в pipe. Проверяем 1235.

# Заблокирован на epoll (ждёт событий — нормально для event loop):
epoll_wait(5, <unfinished ...>
# Сервер в idle ждёт новых соединений. Это нормально.

# Попытка заблокировать файл (другой процесс держит lock):
fcntl(4</var/run/app.lock>, F_SETLKW, {l_type=F_WRLCK, ...}) = ?
# F_SETLKW = блокирующая блокировка файла. Кто-то держит lock.
# Проверяем: fuser /var/run/app.lock

Сценарий 4: Что реально запускает программа через subprocess

Симптом: программа запускает дочерние процессы, нужно знать точные команды.

# Трассировка только вызовов управления процессами
strace -f -tt -e trace=%process,execve -o /tmp/proc.log ./app

# Анализ: что запускалось
grep "execve" /tmp/proc.log
# Вывод покажет точные аргументы каждого запущенного процесса:
1234 execve("./app", ["./app", "--config", "/etc/app.conf"], ...) = 0
1235 execve("/bin/bash", ["bash", "-c", "mysqldump -u root -pSECRET mydb > /backup/db.sql"], ...) = 0
#                                              ^^^^^^^^^^^^^^^^
#                              ВНИМАНИЕ: пароль виден в аргументах!
1236 execve("/usr/bin/gzip", ["gzip", "/backup/db.sql"], ...) = 0

# Ошибка запуска:
1237 execve("/usr/bin/missing-tool", ...) = -1 ENOENT
# → /usr/bin/missing-tool не установлен!

Сценарий 5: Профилирование — где тратится время

# Шаг 1: общая статистика
strace -c -f ./slow_app
# % time     seconds  usecs/call     calls    errors syscall
#  78.34    5.123456        5123      1000           read
#  15.67    1.024680        1024      1000           write
#   4.12    0.269312          89      3026           futex
#   1.87    0.122345          40      3026           poll

# → read занимает 78% времени! Подозрительно.

# Шаг 2: детальная трассировка read с временем
strace -f -T -e trace=read -o /tmp/read.log ./slow_app

# Шаг 3: найти самые медленные вызовы
grep -oP 'read\([^)]+\) = \d+ <\K[0-9.]+' /tmp/read.log | sort -rn | head -20
# 2.341567   ← 2.3 секунды на один read? Это проблема!
# 1.123456
# 0.891234
# ...

# Шаг 4: найти контекст медленного вызова
grep "2.341" /tmp/read.log
# 14:32:01.123456 read(7</dev/sda>, "...", 65536) = 65536 <2.341567>
# → Медленное чтение с диска! Нужен кеш или оптимизация I/O.

Сценарий 6: Анализ конфигурации для chroot / AppArmor

# Собрать все файлы, которые программа пытается открыть
strace -f -e trace=openat -o /tmp/opens.log ./app

# Успешно открытые (нужны в chroot):
grep -v ENOENT /tmp/opens.log | grep -oP '"[^"]+"' | sort -u > /tmp/needed_files.txt

# Файлы, которые не нашла (возможно тоже нужны):
grep ENOENT /tmp/opens.log | grep -oP '"[^"]+"' | sort -u > /tmp/missing_files.txt

# Результат needed_files.txt покажет:
# "/etc/ld.so.cache"
# "/lib/x86_64-linux-gnu/libc.so.6"
# "/etc/hosts"
# "/etc/resolv.conf"
# ...
# Всё это нужно скопировать в chroot.

Сценарий 7: Тестирование устойчивости к ошибкам

# Тест 1: что происходит при недоступности конфига?
strace -e inject=openat:error=ENOENT:when=1 ./app
# Падает с SIGSEGV? → нет проверки возвращаемого значения open!
# Выводит понятное сообщение? → хороший код.
# Молча продолжает с defaults? → приемлемо.

# Тест 2: что при потере подключения к БД на 5-м запросе?
strace -e inject=connect:error=ECONNRESET:when=5 ./app
# Есть ли механизм reconnect?

# Тест 3: что при медленном диске? (каждая запись занимает 1 секунду)
strace -e inject=write:delay_exit=1s:when=1+ ./app
# Не зависает ли UI? Корректны ли таймауты?

# Тест 4: что при полном диске (начиная с 10-й записи)?
strace -e inject=write:error=ENOSPC:when=10+ ./app
# Есть ли graceful handling? Или данные теряются?

12. Быстрые рецепты

# ═══════════════════════════════════════════════════════════════
# ДИАГНОСТИКА ОШИБОК
# ═══════════════════════════════════════════════════════════════

# Только ошибки (лучшее начало для любой проблемы)
strace -f -Z -y ./app

# Ошибки только файловой системы (нет файла, нет прав)
strace -f -Z -y -e trace=%file ./app

# Ошибки только сетевые (отказ, таймаут)
strace -f -Z -yy -e trace=%network ./app

# Записать ошибки в файл и сразу показать
strace -f -Z -y -o /tmp/errors.log ./app; cat /tmp/errors.log

# ═══════════════════════════════════════════════════════════════
# ФАЙЛЫ И БИБЛИОТЕКИ
# ═══════════════════════════════════════════════════════════════

# Все открываемые файлы с путями
strace -y -e trace=openat ./app

# Только успешно открытые файлы (без "не найденных")
strace -y -e trace=openat -e status=successful ./app

# Что происходит с конкретным файлом
strace -y -P /path/to/file ./app

# Поиск отсутствующих библиотек
strace -e trace=openat ./app 2>&1 | grep "ENOENT.*\.so"

# ═══════════════════════════════════════════════════════════════
# СЕТЬ
# ═══════════════════════════════════════════════════════════════

# Все сетевые вызовы с полными деталями
strace -f -tt -T -yy -e trace=%network -o /tmp/net.log ./app

# Только подключения (куда коннектится программа)
strace -yy -e trace=connect ./app

# DNS-запросы (sendto на порт 53)
strace -yy -e trace=sendto ./app 2>&1 | grep ":53"

# ═══════════════════════════════════════════════════════════════
# ЗАВИСАНИЕ И ПРОИЗВОДИТЕЛЬНОСТЬ
# ═══════════════════════════════════════════════════════════════

# Что делает зависший процесс (attach)
strace -p PID -f -tt -T -o /tmp/hang.log

# Быстрая статистика (что занимает CPU в ядре)
strace -c ./app

# Статистика + вывод программы (оба сразу)
strace -C ./app

# Найти медленные вызовы (>100мс)
strace -tt -T -o /tmp/perf.log ./app
awk -F'[<>]' '$NF > 0.1 {print}' /tmp/perf.log

# ═══════════════════════════════════════════════════════════════
# ПРОЦЕССЫ
# ═══════════════════════════════════════════════════════════════

# Дерево дочерних процессов с командами
strace -f -e trace=%process,execve -o /tmp/proc.log ./app
grep execve /tmp/proc.log

# Многопоточное приложение (раздельные файлы)
strace -ff -o /tmp/trace ./app

# Что происходит при создании потоков
strace -f -e trace=clone,futex ./app

# ═══════════════════════════════════════════════════════════════
# ПРОДВИНУТЫЕ
# ═══════════════════════════════════════════════════════════════

# Стек вызовов при каждом write
strace -k -e trace=write ./app

# Тест: программа без конфига
strace -e inject=openat:error=ENOENT:when=1 ./app

# Тест: медленная сеть
strace -e inject=connect:delay_enter=2s:when=1+ ./app

# Тест: полный диск начиная с 5-й записи
strace -e inject=write:error=ENOSPC:when=5+ ./app

# Дамп HTTP-запросов (читаем из сокетов)
strace -yy -e trace=read,recvfrom -s 4096 ./app 2>&1 | grep -A2 "HTTP"

13. Ограничения и альтернативы

Ограничения strace

┌─────────────────────────────────────────────────────────────────┐
│  ОГРАНИЧЕНИЕ          │  ПОЧЕМУ                │  ОБХОДНОЙ ПУТЬ │
├───────────────────────┼────────────────────────┼────────────────┤
│  Замедление 10-100x   │  ptrace = 4+ переключ. │  -Z, -e trace= │
│                       │  контекста на syscall  │  bpftrace, perf│
├───────────────────────┼────────────────────────┼────────────────┤
│  Запрет attach        │  Yama ptrace_scope=1   │  sudo или      │
│  Operation not        │  или нет прав на proc  │  изменить scope│
│  permitted            │                        │                │
├───────────────────────┼────────────────────────┼────────────────┤
│  setuid программы     │  ptrace снимает setuid │  strace setuid │
│  теряют привилегии    │  при трассировке       │  as root       │
├───────────────────────┼────────────────────────┼────────────────┤
│  Только syscalls      │  ltrace для библиотек, │  ltrace        │
│  (не libc функции)    │  gdb для всего         │  gdb/lldb      │
├───────────────────────┼────────────────────────┼────────────────┤
│  init (PID 1)         │  Запрещено ядром       │  Нет способа   │
│                       │  намеренно             │                │
├───────────────────────┼────────────────────────┼────────────────┤
│  Возможный EINTR      │  SIGSTOP при attach    │  Учитывать в   │
│  при attach           │  может прервать вызов  │  производств.  │
└───────────────────────┴────────────────────────┴────────────────┘

Проверка и изменение ptrace_scope

# Проверить текущее значение
cat /proc/sys/kernel/yama/ptrace_scope

# Временно разрешить для сессии (требует root)
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

# Постоянно (в /etc/sysctl.d/)
echo 'kernel.yama.ptrace_scope = 0' | sudo tee /etc/sysctl.d/10-ptrace.conf
sudo sysctl -p /etc/sysctl.d/10-ptrace.conf

Инструменты для разных задач

ЗАДАЧА                                  ИНСТРУМЕНТ
────────────────────────────────────────────────────────────────
Системные вызовы одного процесса   →   strace
Вызовы библиотек (malloc, fopen)   →   ltrace
Syscalls без overhead (production) →   perf trace
Кастомный мониторинг по событиям   →   bpftrace
Готовые eBPF утилиты               →   bcc-tools (bpfcc)
Профилирование CPU (горячие ф-ции) →   perf record + perf report
Долгосрочный security audit        →   sysdig / falco
Отладка с изменением состояния     →   gdb / lldb

Краткий справочник по perf trace (альтернатива с низким overhead)

# Установка
sudo apt install linux-tools-$(uname -r)   # Debian/Ubuntu
sudo dnf install perf                       # RHEL/Fedora

# Трассировка как strace, но через perf_events (меньше overhead)
sudo perf trace -p <PID>
sudo perf trace -e openat,read,write ./app

# Только медленные вызовы (дольше 10мс)
sudo perf trace --duration 10 ./app

# Профилирование: куда уходит CPU
sudo perf record -F 99 -g -p <PID> -- sleep 30
sudo perf report

Краткий справочник по bpftrace

# Установка
sudo apt install bpftrace   # Ubuntu 18.10+

# Какие файлы открывает конкретный процесс
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat
    /pid == 1234/
    { printf("%s\n", str(args->filename)); }'

# Гистограмма времени выполнения read
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; }
    tracepoint:syscalls:sys_exit_read /@start[tid]/
    { @ns = hist(nsecs - @start[tid]); delete(@start[tid]); }'

# Мониторинг всех connect в системе
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_connect
    { printf("%s → %s\n", comm, str(args->uservaddr)); }'

14. Частые системные вызовы

Файловые операции

Вызов Описание Типичные флаги
openat(AT_FDCWD, path, flags, mode) Открыть файл O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_APPEND, O_TRUNC
read(fd, buf, count) Читать из FD
write(fd, buf, count) Писать в FD
close(fd) Закрыть FD
stat(path, statbuf) Инфо о файле по пути
fstat(fd, statbuf) Инфо о файле по FD
lstat(path, statbuf) Инфо о файле (не следует симлинкам)
access(path, mode) Проверить права F_OK, R_OK, W_OK, X_OK
unlinkat(dirfd, path, flags) Удалить файл/директорию
renameat(olddir, old, newdir, new) Переименовать
mkdirat(dirfd, path, mode) Создать директорию
pread64(fd, buf, count, offset) Читать с позиции (без seek)
pwrite64(fd, buf, count, offset) Писать с позиции (без seek)

Управление процессами

Вызов Описание Ключевые флаги clone
clone(flags, stack, ...) Создать процесс или поток CLONE_VM (общая память=поток), SIGCHLD (новый процесс)
execve(path, argv, envp) Запустить программу
exit_group(status) Завершить все потоки процесса
wait4(pid, status, options, rusage) Ждать завершения дочернего WNOHANG (не блокировать)
getpid() PID текущего процесса
getppid() PID родительского процесса
kill(pid, sig) Послать сигнал процессу
tkill(tid, sig) Послать сигнал потоку

Память

Вызов Описание Типичный вывод
mmap(addr, len, prot, flags, fd, off) Отобразить память/файл Адрес в памяти
munmap(addr, len) Освободить отображение 0
mprotect(addr, len, prot) Изменить права страниц PROT_READ, PROT_WRITE, PROT_EXEC
madvise(addr, len, advice) Подсказка ядру о памяти MADV_DONTNEED, MADV_SEQUENTIAL
brk(addr) Изменить конец сегмента данных Новый адрес конца

Сеть

Вызов Описание Ключевые параметры
socket(domain, type, proto) Создать сокет AF_INET/AF_INET6, SOCK_STREAM/SOCK_DGRAM
connect(fd, addr, addrlen) Подключиться к адресу Структура sockaddr
bind(fd, addr, addrlen) Привязать к адресу Структура sockaddr
listen(fd, backlog) Ждать входящих соединений backlog = размер очереди
accept4(fd, addr, addrlen, flags) Принять соединение Возвращает новый FD
sendto(fd, buf, len, flags, addr, addrlen) Отправить данные MSG_NOSIGNAL
recvfrom(fd, buf, len, flags, addr, addrlen) Получить данные MSG_WAITALL
setsockopt(fd, level, optname, val, len) Настройки сокета SO_REUSEADDR, SO_TIMEOUT

Синхронизация и ожидание

Вызов Описание Когда встречается
futex(addr, op, val, timeout, ...) Быстрый мьютекс Основа pthreads, блокировки
epoll_wait(epfd, events, maxevents, timeout) Ждать событий (современный) Event loop серверов
poll(fds, nfds, timeout) Ждать событий на FD Старый вариант
select(nfds, rfds, wfds, efds, timeout) Ждать событий (устаревший) Старый код
nanosleep(req, rem) Сон с точностью до нс sleep(), usleep()
pause() Ждать любого сигнала Зависание до сигнала
clock_nanosleep(clk, flags, req, rem) Сон по конкретным часам Высокоточные таймеры

Стандартные дескрипторы и типичные значения FD

FD = 0  →  stdin   (чтение с клавиатуры или pipe)
FD = 1  →  stdout  (вывод на экран или в pipe)
FD = 2  →  stderr  (ошибки на экран)
FD = 3  →  первый открытый программой файл/сокет
FD = 4+ →  следующие файлы, сокеты, pipe-ы

С флагом -y strace покажет:
FD 3</etc/config.yaml>                    ← файл
FD 4<TCP:[127.0.0.1:8080->10.0.0.1:54321]> ← TCP сокет
FD 5<UDP:[0.0.0.0:53]>                    ← UDP сокет
FD 6<pipe:[12345]>                        ← pipe
FD 7<anon_inode:eventfd>                  ← eventfd
FD 8<anon_inode:[epoll]>                  ← epoll

💡 Стратегия использования: начните с strace -c ./app для обзора, затем strace -Z -y ./app для быстрого поиска ошибок. Добавляйте -e trace=ГРУППА чтобы сфокусироваться на нужной области. Используйте -f почти всегда — современные программы активно используют fork/threads.

🔍 Главное правило: если видите -1 ECODE — это ваш ответ. Строка с ошибкой обычно прямо указывает на проблему: что не нашли, к чему нет доступа, что не работает.

📚 Подробная документация: man 1 strace, man 2 syscall, man 2 ptrace