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

📖 Полное руководство по 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