📖 Полное руководство по strace¶
strace — утилита для трассировки системных вызовов и сигналов в Linux. Незаменима при отладке, диагностике и изучении поведения программ без доступа к исходному коду.
📋 Содержание¶
- Что такое strace и как это работает
- Установка
- Синтаксис команды
- Чтение вывода strace
- Основные опции
- Фильтрация системных вызовов
- Работа со временем и статистикой
- Работа с файлами и дескрипторами
- Работа с процессами и потоками
- Продвинутые возможности
- Практические сценарии с разбором
- Быстрые рецепты
- Ограничения и альтернативы
- Частые системные вызовы
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¶
RHEL / AlmaLinux / Fedora¶
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¶
Анатомия одной строки¶
Разберём подробно каждую часть:
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 — видим только номер:
С -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¶
Показывает цепочку вызовов функций, которая привела к системному вызову:
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 мс → Ядро выполняет → возвращает результат
Синтаксис¶
Параметры инъекции¶
| Параметр | Значение | Пример |
|---|---|---|
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