В этом уроке разберём трассировку библиотек в Linux: что такое ltrace linux, чем он отличается от strace, как читать его вывод по полям, где он реально спасает, а где бесполезен, и чем его заменяют в 2026 году, когда старичок ltrace начинает капризничать. Будут живые ltrace примеры, которые можно повторить руками прямо сейчас.
Уровень библиотек против syscalls: что вообще ловит ltrace
Любая обычная программа на Linux линкуется с динамическими библиотеками - в первую очередь с libc (glibc). Когда код вызывает malloc, strcpy, printf, fopen - это вызовы функций библиотеки, а не системные вызовы ядра. Часть из них внутри сама дёрнет syscall (тот же fopen в итоге позовёт openat()), а часть вообще не доходит до ядра: strlen считает длину строки прямо в памяти, ядро тут не при делах.
Граница, на которой работает каждый инструмент:
- strace перехватывает границу "программа - ядро" (системные вызовы).
- ltrace перехватывает границу "программа - библиотека" (вызовы функций .so).

Запуск и базовые флаги: ltrace примеры на практике
Сначала поставь утилиту:
Код: Выделить всё
sudo apt install ltraceКод: Выделить всё
sudo dnf install ltraceСамый простой запуск - имя команды прямо после ltrace:
Код: Выделить всё
$ ltrace ls
__libc_start_main(0x401a30, 1, 0x7ffd..., ...)
strrchr("ls", '/') = nil
setlocale(LC_ALL, "") = "C.UTF-8"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
malloc(5) = 0x55a3c1b0
opendir(".") = 0x55a3c1e0
readdir(0x55a3c1e0) = 0x55a3c210
...
fwrite("file1\n", 1, 6, 0x7f...) = 6
+++ exited (status 0) +++
Теперь полезные флаги.
-p PID - подцепиться к уже работающему процессу (нужен sudo, если процесс чужой):
Код: Выделить всё
$ sudo ltrace -p 4567
Код: Выделить всё
$ ltrace -f ./myscript.sh
Код: Выделить всё
$ ltrace -e 'malloc+free+strcpy+strlen' ./app
Код: Выделить всё
$ ltrace -e '@libssl.so*' ./mytls-client
SSL_new(0x55b2...) = 0x55b2...
SSL_connect(0x55b2...) = -1
SSL_get_error(0x55b2..., -1) = 1
Полезный напарник фильтра - -x для трассировки символов по имени (не только PLT, но и экспортируемых из библиотек) и -C / --demangle, который раскручивает изуродованные C++-имена в читаемые (_ZN3foo3barEv превращается в foo::bar()). Для C++-бинарника без -C вывод выглядит как набор крякозябр.
-c - сводная статистика вместо потока. Очень удобно понять, куда уходит время и что зовётся чаще всего:
Код: Выделить всё
$ ltrace -c ls
% time seconds usecs/call calls function
------ ----------- ----------- --------- --------------------
28.57 0.000040 8 5 strlen
21.43 0.000030 15 2 readdir64
14.29 0.000020 10 2 opendir
7.14 0.000010 10 1 fopen
------ ----------- ----------- --------- --------------------
100.00 0.000140 14 total
-S - показать заодно и системные вызовы (с префиксом SYS_). Это и есть знаменитая связка двух уровней в одном выводе:
Код: Выделить всё
$ ltrace -S -e 'fopen+fread' ./app
fopen("/etc/hosts", "r") = 0x55c1...
SYS_openat(AT_FDCWD, "/etc/hosts", 0) = 3
fread(0x55c1..., 1, 4096, 0x55c1...) = 219
SYS_read(3, "127.0.0.1 localhost\n"..., 4096) = 219
Когда ltrace полезнее strace, а когда наоборот
ltrace берёт верх, когда вопрос звучит как "какую функцию библиотеки зовёт программа и с какими данными". Классика:
- разобрать работу с libc и понять, что приложение зовёт getenv() и читает не ту переменную окружения;
- разобрать обращения к sqlite (sqlite3_prepare_v2, sqlite3_step) - видно сам SQL прямо в аргументах;
- поймать проблему в openssl/TLS на уровне рукопожатия;
- увидеть открытый текст до шифрования: ltrace -e 'SSL_write+SSL_read' на клиенте/сервере покажет данные ДО того, как они попадут в TLS.
strace полезнее, когда баг лежит на границе с ядром: программа зависла на read() из сети, ловит EACCES при открытии файла, упёрлась в лимит дескрипторов, ждёт futex. Syscall - это факт взаимодействия с системой, и для "почему не открывается файл" strace точнее и стабильнее.
Боевой приём: запусти оба. ltrace покажет, ЧТО приложение хотело сделать на уровне логики, strace - ВО ЧТО это вылилось на уровне ядра. Часто истина находится ровно на стыке.
Актуально на 2026: ltrace стареет, eBPF взрослеет
Честный разговор о состоянии инструмента в 2026 году. ltrace - проект с тяжёлой судьбой: разработка mainline почти заглохла, активность держится на форке dkogan/ltrace. На свежих ядрах (6.x) и в Fedora Rawhide ltrace периодически падает или зависает при трассировке некоторых бинарников (известны крэши даже на bash), хуже дружит с PIE (position-independent executables, которые сейчас собираются по умолчанию) и с IFUNC-резолверами glibc, чем strace. Это не значит, что он мёртв - на типовых задачах он работает и остаётся самым быстрым способом подсмотреть аргументы libc/openssl. Но держи в голове запасной план.
Этот запасной план в 2026 - eBPF, а конкретно bpftrace с механизмом uprobe (пользовательские точки останова, аналог kprobe для userspace). Где ltrace грубо ставит брейкпоинты через ptrace и сильно тормозит процесс, bpftrace вешает лёгкий зонд в ядре и почти не мешает работе - оверхед на порядки ниже. Пример, эквивалентный ltrace -e SSL_write: трассируем вызов SSL_write в libssl у живого процесса по PID:
Код: Выделить всё
# bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libssl.so.3:SSL_write { printf("SSL_write %d bytes\n", arg2); }' -p 4567
Типичные грабли и заблуждения
- Пустой вывод на статике. Бинарь слинкован статически (или это Go-программа) - ltrace покажет почти ничего и может ругнуться, что не нашёл символов. Это не баг, перехватывать просто нечего. Проверь линковку: и
Код: Выделить всё
file ./app. Если ldd говорит "not a dynamic executable" - ltrace бесполезен, бери strace (для syscalls) или bpftrace/uprobe по имени символа.Код: Выделить всё
ldd ./app - Overhead выше, чем у strace. Библиотечных вызовов на порядки больше, чем системных (один fprintf - это куча malloc/strlen/memcpy внутри). Каждый перехват через ptrace тормозит программу. На нагруженном процессе без фильтра -e легко получить замедление в десятки раз и поломать тайминги, которые ты пытаешься измерить. Всегда сужай фокус через -e/-x.
- Падения и зависания на свежих системах. На ядрах 6.x и новых дистрибутивах ltrace бывает нестабилен (PIE, IFUNC, seccomp). Если он крэшится или -S молчит - это не твоя ошибка, переключайся на bpftrace.
- "ltrace показывает не все функции". По умолчанию он видит вызовы через PLT - то есть наружу, в чужие библиотеки. Внутренние (static) функции самого приложения и инлайненные вызовы он не трассирует, это нормально. Для произвольной внутренней функции нужен uprobe по символу.
- Защита и права. Подцепиться к чужому процессу (-p) мешает та же политика ptrace_scope (/proc/sys/kernel/yama/ptrace_scope), что и у strace. Если "Operation not permitted" - запускай через sudo или временно ослабь ptrace_scope.
- C++ без -C. Для C++-бинарника без флага --demangle имена выглядят как _ZN... - не пугайся, добавь -C.
- Запусти и посмотри, сколько раз ls дёргает аллокатор.
Код: Выделить всё
ltrace -e 'malloc+free' ls - Сделай статистику: - найди функцию с самым большим числом calls и пойми, что значит её usecs/call.
Код: Выделить всё
ltrace -c date - Сравни уровни: - найди в выводе, в какой SYS_-вызов превратился fopen (если -S молчит на твоей сборке - это тот самый баг 2026, открой второй терминал со strace -p).
Код: Выделить всё
ltrace -S -e 'fopen+fread+fclose' cat /etc/hostname - Проверь ограничение: (динамика, ltrace работает) против любого Go-бинарника
Код: Выделить всё
ldd /bin/ls(часто статика, ltrace молчит).Код: Выделить всё
file ./gobin - Современная замена: установи bpftrace и сними тот же SSL_write через uprobe у curl: - сравни ощущения с ltrace по скорости и шуму.
Код: Выделить всё
sudo bpftrace -e 'uprobe:libssl:SSL_write { printf("write %d\n", arg2); }' -c 'curl -s https://example.com'
- Чем уровень, который трассирует ltrace, отличается от уровня strace, и через какой механизм (что за PLT) ltrace это ловит?
- Что выведет ltrace на статически слинкованном бинарнике (Go) и почему? Чем его заменить в этом случае?
- Какой флаг даст сводку "функция - число вызовов - время", и что означает колонка usecs/call?
- Почему в 2026 для нагруженного прода или контейнеров ltrace часто заменяют на bpftrace с uprobe?
ltrace - это трассировка вызовов библиотек: malloc, printf, SSL_connect и прочее, что программа зовёт через PLT. Он отвечает на вопрос "какую функцию и с какими аргументами", strace - на вопрос "что ушло в ядро". Базовый набор: запуск по имени или -p PID, -f для детей, -e/-x для фильтра (обязательно, иначе шум и тормоза), -C для C++-имён, -c для статистики, -S для связки с syscalls. Работает только на динамике, overhead выше strace - поэтому фокусируй вывод. На 2026 ltrace местами нестабилен на свежих ядрах: когда он капризничает или нужен лёгкий зонд без тормозов, бери bpftrace + uprobe. А когда не уверен, на каком уровне баг - гоняй strace и ltrace вместе, истина обычно на стыке.