ltrace: трассировка вызовов библиотек

Рейтинг: 70.1% · 9 голосов
Подробный курс по диагностике и производительности Linux для админов и DevOps: с чего начать, методология USE, логи (journalctl, dmesg), процессы, CPU, память и OOM, диск и IO (iostat), сеть (ss, tcpdump), системные вызовы и strace, ltrace, perf, флеймграфы, ftrace, eBPF и bpftrace, lsof, мониторинг. Разбор каждого инструмента с чтением вывода.
Ответить
Аватара пользователя
Dmitry_SRE
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

ltrace: трассировка вызовов библиотек

Сообщение Dmitry_SRE »

Оглавление курса (47)
  1. С чего начать диагностику Linux: методология вместо паники
  2. Первые 60 секунд: экспресс-диагностика нагруженного сервера
  3. Методологии диагностики: USE, RED и здравый смысл
  4. Логи systemd через journalctl: где искать причину
  5. Где лежат логи Linux: /var/log, dmesg и rsyslog
  6. Источники правды: load average, /proc и /sys
  7. Процессы Linux: ps, pstree и состояния процессов
  8. Интерактивный мониторинг: top и htop
  9. Метрики процессов во времени: pidstat
  10. Приоритеты и ограничения: nice, ionice, cgroups
  11. Сигналы и зависшие процессы: kill, и что делать с D-state
  12. Загрузка CPU: user, system, iowait, steal и контекст-свитчи
  13. Диагностика CPU по ядрам: vmstat и mpstat
  14. Профилирование CPU: perf top и поиск пожирателя
  15. Частоты, троттлинг и NUMA: turbostat и numastat
  16. Память Linux: RSS, VSZ, page cache и миф о нехватке памяти
  17. Сколько памяти занято: free, /proc/meminfo и vmstat
  18. Поиск утечек и пожирателей памяти: smem, pmap, smaps
  19. Swap и подкачка: swapon, swappiness, когда своп - это боль
  20. OOM killer: кто и за что убил процесс
  21. Память ядра и slab: slabtop и куда уходит RAM
  22. Подсистема ввода-вывода: путь запроса от приложения до диска
  23. Диагностика диска: iostat и чтение await, %util
  24. Кто грузит диск: iotop и атрибуция io процессам
  25. Файловые системы: df, du, иноды и куда делось место
  26. Глубокая диагностика блочного слоя: blktrace и biolatency
  27. Кеш страниц и грязные данные: dirty pages, fsync, drop_caches
  28. Сетевой стек Linux: путь пакета и где возникают задержки
  29. Сокеты и соединения: ss и netstat
  30. Захват трафика: tcpdump для диагностики сети
  31. Задержки и потери в сети: ping, mtr, диагностика латентности
  32. Сеть как источник проблем: nftables, conntrack, дропы
  33. Что такое системный вызов и зачем его трассировать
  34. strace: трассировка системных вызовов на практике
  35. strace в бою: почему программа висит, падает или тормозит
  36. ltrace: трассировка вызовов библиотек (вы здесь)
  37. Накладные расходы трассировки и безопасные альтернативы
  38. perf: универсальный профайлер Linux
  39. Флеймграфы: визуализация профиля производительности
  40. Трассировка ядра: ftrace и trace-cmd
  41. eBPF: революция в наблюдаемости Linux
  42. Инструменты eBPF на практике: BCC и bpftrace
  43. lsof: открытые файлы, дескрипторы, кто держит файл и порт
  44. Разбор кейса: приложение тормозит - пошаговая диагностика
  45. Непрерывный мониторинг: sar, node_exporter, Grafana, Zabbix
  46. Диагностика в контейнерах: cgroups, лимиты, Docker и Kubernetes
  47. Карта инструментов и сквозной разбор инцидента производительности
Представь: strace тебе уже показал, что программа дёргает ядро - открывает файлы, шлёт байты в сокет, читает /etc. Но ты всё ещё не понимаешь, ПОЧЕМУ она это делает. Какую функцию из libc или openssl она вызвала, с какими аргументами, что ей вернулось? Syscall-уровень тут немой: он видит финальный read() из сокета, но не видит, что перед этим программа позвала SSL_connect() и упёрлась в просроченный сертификат. Вот тут на сцену выходит ltrace - старший брат strace, который смотрит на этаж выше.

В этом уроке разберём трассировку библиотек в Linux: что такое ltrace linux, чем он отличается от strace, как читать его вывод по полям, где он реально спасает, а где бесполезен, и чем его заменяют в 2026 году, когда старичок ltrace начинает капризничать. Будут живые ltrace примеры, которые можно повторить руками прямо сейчас.

Уровень библиотек против syscalls: что вообще ловит ltrace

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

Граница, на которой работает каждый инструмент:
  • strace перехватывает границу "программа - ядро" (системные вызовы).
  • ltrace перехватывает границу "программа - библиотека" (вызовы функций .so).
Технически ltrace работает так: динамически слинкованный бинарь зовёт чужие функции не напрямую, а через таблицу PLT (Procedure Linkage Table) - это набор переходников, через которые код приложения прыгает в код библиотеки. ltrace ставит точку останова (по сути аппаратно через ptrace) на каждую запись PLT и таким образом видит каждый вызов наружу - в libc, libssl, libsqlite3 и любую другую .so. Отсюда сразу следует ключевое ограничение: трассировка библиотек linux работает только для динамической линковки. На статически собранном бинарнике (а это почти все программы на Go и многие на Rust с musl) ltrace покажет пустоту - перехватывать нечего, отдельной PLT-границы туда нет.

Изображение

Запуск и базовые флаги: ltrace примеры на практике

Сначала поставь утилиту:

Код: Выделить всё

sudo apt install ltrace
на Debian/Ubuntu/Astra Linux или

Код: Выделить всё

sudo dnf install ltrace
на Fedora/RHEL/RED OS. Версия по состоянию на 2026 - ветка 0.7.91/dkogan-форк (форк cespedes/ltrace, mainline на github.com/dkogan/ltrace); классический "ltrace 0.7.3" из старых дистрибутивов всё ещё встречается, но новые сборки берут именно форк, в нём чинят поддержку PIE и IFUNC.

Самый простой запуск - имя команды прямо после 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) +++
Как это читать. Слева - имя библиотечной функции. В скобках - аргументы (ltrace знает прототипы стандартных функций из своих конфигов в /usr/share/ltrace/, поэтому строки показывает как строки, а указатели как адреса, а не как голые числа). После = - возвращённое значение. Видно: malloc(5) вернул адрес 0x55a3c1b0, opendir(".") открыл текущий каталог и вернул указатель на DIR, readdir прошёлся по записям, fwrite напечатал строку и вернул 6 (число выведенных байт). Если функция возвращает nil - это NULL, частый признак ошибки: не нашлось, не выделилось, не открылось. Строка +++ exited (status 0) +++ - программа штатно завершилась с кодом 0; если бы её убил сигнал, увидел бы --- SIGSEGV --- и +++ killed by SIGSEGV +++.

Теперь полезные флаги.

-p PID - подцепиться к уже работающему процессу (нужен sudo, если процесс чужой):

Код: Выделить всё

$ sudo ltrace -p 4567
-f - следовать за порождёнными процессами (fork/clone). Без него ты увидишь только родителя, а вся работа может идти в детях. С -f каждая строка получает префикс PID в квадратных скобках:

Код: Выделить всё

$ ltrace -f ./myscript.sh
-e (или -L) - фильтр по именам функций. Это спасение, потому что без фильтра вывод тонет в сотнях malloc/free. Хочешь видеть только работу со строками:

Код: Выделить всё

$ 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
Тут сразу видно: SSL_connect вернул -1 (ошибка), а SSL_get_error выдал код 1 (SSL_ERROR_SSL - провал на уровне протокола, часто кривой/просроченный сертификат). Это и есть та логика, которую strace тебе не покажет: ты ловишь баг в TLS-рукопожатии без исходников приложения.

Полезный напарник фильтра - -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
Колонки: % time - доля времени на эту функцию, seconds - суммарное время в секундах, usecs/call - среднее время одного вызова в микросекундах, calls - сколько раз позвали, function - имя. Куда смотреть: большой calls у мелкой функции = программа долбит её в цикле (кандидат на оптимизацию); большой % time = тут реально проводится время. Важная оговорка: время тут включает оверхед самой трассировки, поэтому абсолютные секунды условны - смотри на пропорции и счётчики вызовов, а не на точные миллисекунды.

-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
Сверху видно намерение (fopen с режимом "r"), снизу - как оно легло в ядро (openat вернул дескриптор 3, потом read по этому дескриптору прочитал 219 байт). Это лучший способ объяснить новичку, как один высокоуровневый вызов превращается в syscall. На некоторых сборках 2026 года связка -S капризничает или ничего не выводит из-за изменений в seccomp/ptrace - если так, используй два терминала (ltrace в одном, strace -p в другом) или сразу bpftrace, о котором ниже.

Когда 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
Тут uprobe цепляется ко входу функции (аргументы доступны как arg0, arg1, arg2 - это указатель SSL, буфер и длина), а uretprobe на тот же символ дал бы возвращаемое значение через retval. bpftrace работает и на динамике, и - в отличие от ltrace - даже на статике и Go-бинарниках, если знаешь имя/смещение символа (для Go удобнее цеплять по имени функции рантайма). Практический вывод 2026: для разовой ручной отладки "что в аргументах" ltrace всё ещё быстрее набрать, для нагруженного прода, контейнеров и всего, что нельзя тормозить, - бери bpftrace/uprobe. Знание обоих - норма для middle-инженера.

Типичные грабли и заблуждения
  • Пустой вывод на статике. Бинарь слинкован статически (или это Go-программа) - ltrace покажет почти ничего и может ругнуться, что не нашёл символов. Это не баг, перехватывать просто нечего. Проверь линковку:

    Код: Выделить всё

    file ./app
    и

    Код: Выделить всё

    ldd ./app
    . Если ldd говорит "not a dynamic executable" - ltrace бесполезен, бери strace (для syscalls) или bpftrace/uprobe по имени символа.
  • 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.
Мини-лаба: повтори руками прямо сейчас
  • Запусти

    Код: Выделить всё

    ltrace -e 'malloc+free' ls
    и посмотри, сколько раз ls дёргает аллокатор.
  • Сделай статистику:

    Код: Выделить всё

    ltrace -c date
    - найди функцию с самым большим числом calls и пойми, что значит её usecs/call.
  • Сравни уровни:

    Код: Выделить всё

    ltrace -S -e 'fopen+fread+fclose' cat /etc/hostname
    - найди в выводе, в какой SYS_-вызов превратился fopen (если -S молчит на твоей сборке - это тот самый баг 2026, открой второй терминал со strace -p).
  • Проверь ограничение:

    Код: Выделить всё

    ldd /bin/ls
    (динамика, ltrace работает) против любого Go-бинарника

    Код: Выделить всё

    file ./gobin
    (часто статика, ltrace молчит).
  • Современная замена: установи bpftrace и сними тот же SSL_write через uprobe у curl:

    Код: Выделить всё

    sudo bpftrace -e 'uprobe:libssl:SSL_write { printf("write %d\n", arg2); }' -c 'curl -s https://example.com'
    - сравни ощущения с ltrace по скорости и шуму.
Контрольные вопросы
  • Чем уровень, который трассирует 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 вместе, истина обычно на стыке.
👍 ❤️4 🔥1 😄 🤔
Аватара пользователя
sparkpilot
Сообщения: 1
Зарегистрирован: 02 июн 2026, 04:25

Re: ltrace: трассировка вызовов библиотек

Сообщение sparkpilot »

Спасибо, наконец дошло чем они отличаются. Всегда путал и лепил strace везде, а оказывается на TLS-рукопожатии надо было ltrace смотреть. И про SSL_write до шифрования вообще огонь, не знал.
👍1 ❤️1 🔥 😄 🤔2
Аватара пользователя
Klm717
Сообщения: 1
Зарегистрирован: 11 май 2026, 17:44

Re: ltrace: трассировка вызовов библиотек

Сообщение Klm717 »

А подскажите, у меня на своей программе на Go ltrace вообще ничего не выводит, пустота и ругань про символы. Думал сломал что-то, а это просто статика? ldd говорит not a dynamic executable. Получается мне сразу на bpftrace uprobe идти?
👍2 ❤️ 🔥 😄 🤔2
Ответить
← Предыдущая глава
strace в бою: почему программа висит, падает или тормозит
Следующая глава →
Накладные расходы трассировки и безопасные альтернативы

Все главы курса «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Поделиться темой: ✈ Telegram VK
  • Похожие темы
Похожие запросы: что такое системный вызов и зачем трассироватьМониторинг и метрики nginxЛоги nginx: access_log и JSON

Вернуться в «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость