https://habr.com/ru/companies/badoo/articles/493856/
https://habr.com/ru/companies/first/articles/442738/
https://syscalls.mebeim.net/?table=x86/64/x64/v6.12
Дескриптор файла (File Descriptor, FD) — это целое число, которое операционная система использует для представления открытых файлов или других объектов ввода/вывода (например, сокетов, канала связи и т. д.). Он служит уникальным идентификатором, с помощью которого процесс взаимодействует с файлом или устройством.
Количество доступных для процесса файловых дескрипторов ограничено значением параметра /OPEN_MAX, который задается в файле sys/limits.h. Также можно настроить максимальное количество дескрипторов с помощью флага -n в команде ulimit. Дескрипторы файлов создаются при вызовах таких функций, как open, pipe, creat и fcntl. Каждый процесс обычно использует свой собственный набор дескрипторов, но эти дескрипторы могут быть унаследованы и дочерними процессами, порожденными через функцию fork. Кроме того, дескрипторы можно копировать с использованием функций fcntl, dup и dup2. Как работает дескриптор файла
Когда процесс открывает файл или устройство (например, с помощью системного вызова open() в Linux), операционная система создает дескриптор файла для отслеживания открытого ресурса. Этот дескриптор служит ссылкой, через которую процесс может читать, записывать или управлять этим ресурсом.
- Открытие файла: Когда процесс вызывает функцию открытия файла (например, open()), операционная система возвращает дескриптор, который будет использоваться в дальнейшем для взаимодействия с файлом.
- Чтение/запись: После того как процесс получает дескриптор файла, он может использовать его для выполнения операций ввода/вывода, таких как чтение с помощью read() или запись с помощью write().
- Закрытие файла: Когда процесс больше не нуждается в файле, он вызывает функцию close() для освобождения дескриптора, и операционная система может закрыть файл.
Пример работы с дескриптором файла (на примере C): #include #include int main() { int fd = open("example.txt", O_RDONLY); // Открываем файл для чтения if (fd == -1) { // Ошибка при открытии файла return 1; } char buffer[100]; ssize_t bytesRead = read(fd, buffer, sizeof(buffer)); // Чтение из файла if (bytesRead == -1) { // Ошибка при чтении close(fd); return 1; } write(STDOUT_FILENO, buffer, bytesRead); // Печать содержимого файла в стандартный вывод close(fd); // Закрытие дескриптора return 0; } Kопировать
В этом примере: open(“example.txt”, O_RDONLY) открывает файл для чтения и возвращает дескриптор fd.read(fd, buffer, sizeof(buffer)) использует дескриптор для чтения данных из файла.write(STDOUT_FILENO, buffer, bytesRead) выводит считанные данные на экран.close(fd) закрывает файл и освобождает дескриптор. Важные типы дескрипторов
Стандартные дескрипторы: Это заранее определенные дескрипторы, которые автоматически открыты для каждого процесса:0 — стандартный ввод (stdin).1 — стандартный вывод (stdout).2 — стандартный вывод ошибок (stderr). Роль дескрипторов в операционных системах
Открытие файлов и устройств: Дескриптор файла — это способ для операционной системы отслеживать открытые файлы и другие ресурсы ввода/вывода. Он позволяет системе управлять правами доступа и выполнять операции ввода/вывода.
Ресурсы ввода/вывода: Помимо файлов, дескрипторы используются для работы с другими объектами ввода/вывода, такими как сокеты, каналы, устройства, и другие файлы, не обязательно физические (например, временные файлы, потоки).
Пример в Linux
В Linux, каждый процесс имеет таблицу дескрипторов файлов, где индекс каждого элемента — это сам дескриптор, а значение элемента — это структура данных, которая хранит информацию о соответствующем открытом ресурсе. Например, когда процесс открывает файл, операционная система присваивает дескриптор, чтобы отслеживать его состояние, и передает этот дескриптор обратно процессу для дальнейшей работы. Дескриптор файла — это основной механизм взаимодействия процессов с файлами и устройствами ввода/вывода в операционных системах, позволяющий системам эффективно управлять ресурсами.
Лабораторная работа: Применение ftrace для анализа работы ядра Linux
Цель работы
Изучить принципы работы с инструментом ftrace для трассировки вызовов функций ядра Linux, освоить базовые методы настройки и анализа данных, полученных с помощью ftrace.
Оборудование и программное обеспечение
- Операционная система: Linux (ядро ≥ 3.10, рекомендуется Ubuntu/Debian)
- Права: root (для доступа к debugfs)
- Утилиты: bash, grep, less
- Дополнительно: монтирование debugfs (если не выполнено автоматически)
Теоретическая часть
Что такое ftrace?
ftrace — встроенный в ядро Linux инструмент для трассировки выполнения функций ядра. Он позволяет:
- Отслеживать последовательность вызовов функций.
- Анализировать время выполнения функций.
- Изучать взаимодействие компонентов ядра.
- Диагностировать проблемы производительности.
Основные возможности
- Function tracer: запись всех вызовов функций.
- Function graph tracer: отображение вызовов в виде дерева с указанием времени выполнения.
- Event tracing: трассировка конкретных событий ядра (например, переключений задач).
- Stack tracer: анализ использования стека ядра.
Расположение интерфейса
Интерфейс ftrace доступен через debugfs в каталоге:/sys/kernel/debug/tracing/
Ключевые файлы:
current_tracer
— выбор активного трейсера.available_tracers
— список доступных трейсеров.trace
— вывод результатов трассировки.set_ftrace_filter
— фильтрация функций для трассировки.tracing_on
— включение/отключение записи трассировки.
Ход работы
Подготовка окружения
Проверка доступности debugfs:
bashls /sys/kernel/debug/tracing
Если каталог отсутствует, смонтируйте debugfs:
bashsudo mount -t debugfs nodev /sys/kernel/debug
Проверка поддержки ftrace в ядре: Убедитесь, что в конфигурации ядра включены опции:
bashgrep CONFIG_FUNCTION_TRACER /boot/config-$(uname -r) # Должно вернуть: CONFIG_FUNCTION_TRACER=y
Задание 1: Базовая трассировка функций
Выбор трейсера:
bashecho function > /sys/kernel/debug/tracing/current_tracer
Очистка предыдущих данных:
bashecho > /sys/kernel/debug/tracing/trace
Включение трассировки:
bashecho 1 > /sys/kernel/debug/tracing/tracing_on
Выполнение тестовой операции (например, запуск
ls
):bashls / > /dev/null
Остановка трассировки:
bashecho 0 > /sys/kernel/debug/tracing/tracing_on
Просмотр результатов:
bashcat /sys/kernel/debug/tracing/trace
Пример вывода:
# tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | ls-1234 [000] 123456.789: sys_open -> do_sys_open ls-1234 [000] 123456.790: do_sys_open <- sys_open
Задание 2: Фильтрация функций
Трассировка конкретной функции (например,
do_sys_open
):bashecho do_sys_open > /sys/kernel/debug/tracing/set_ftrace_filter echo function > /sys/kernel/debug/tracing/current_tracer echo > /sys/kernel/debug/tracing/trace echo 1 > /sys/kernel/debug/tracing/tracing_on ls / > /dev/null echo 0 > /sys/kernel/debug/tracing/tracing_on cat /sys/kernel/debug/tracing/trace
Анализ вывода: Вывод будет содержать только вызовы
do_sys_open
и связанные с ней функции.
Задание 3: Граф вызовов (function_graph)
Включение graph-трейсера:
bashecho function_graph > /sys/kernel/debug/tracing/current_tracer echo 2 > /sys/kernel/debug/tracing/max_graph_depth # Ограничение глубины echo > /sys/kernel/debug/tracing/trace echo 1 > /sys/kernel/debug/tracing/tracing_on
Выполнение операции (например, чтение файла):
bashcat /proc/cpuinfo > /dev/null
Просмотр дерева вызовов:
bashcat /sys/kernel/debug/tracing/trace
Пример вывода:
# tracer: function_graph # CPU DURATION FUNCTION CALLS # | | | | | | | 0) 0.563 us | } /* vfs_read */ 0) 1.234 us | } /* sys_read */ 0) + 10.123 us | } /* __x64_sys_read */
Задание 4: Сбор статистики
Включение статистического трейсера:
bashecho function > /sys/kernel/debug/tracing/current_tracer echo 1 > /sys/kernel/debug/tracing/tracing_on sleep 5 # Сбор данных в течение 5 секунд echo 0 > /sys/kernel/debug/tracing/tracing_on
Просмотр статистики:
bashcat /sys/kernel/debug/tracing/trace_stat/function0
Пример вывода:
Function Hit Time (ns) Avg (ns) -------------- ----- --------- --------- _raw_spin_unlock_irqrestore 120 45000 375.0 finish_task_switch 80 32000 400.0
Анализ результатов
- Function tracer позволяет увидеть последовательность вызовов, но не показывает вложенность.
- Function graph демонстрирует иерархию вызовов и время выполнения, что критично для поиска узких мест.
- Фильтрация через
set_ftrace_filter
снижает объем данных и упрощает анализ. - Статистика (
trace_stat
) помогает выявить "тяжелые" функции по времени выполнения.
Возможные ошибки и решения
Проблема | Решение |
---|---|
Каталог /sys/kernel/debug/tracing отсутствует | Выполните mount -t debugfs nodev /sys/kernel/debug |
Нет записи в trace после операции | Проверьте tracing_on (должен быть 1 ), убедитесь, что трейсер активен |
Ошибка Operation not permitted | Запустите команды от root (sudo -i ) |
Выводы
- ftrace — мощный инструмент для анализа внутренней работы ядра Linux.
- Базовые трейсеры (
function
,function_graph
) позволяют отслеживать вызовы функций и выявлять проблемы производительности. - Фильтрация и ограничение глубины трассировки критичны для работы с большими объемами данных.
- Инструмент требует осторожности: длительная трассировка может замедлить систему.
Контрольные вопросы
Какие трейсеры доступны в вашей системе?
Проверьте файл/sys/kernel/debug/tracing/available_tracers
.Как остановить трассировку без потери данных?
Запишите0
вtracing_on
, затем прочитайте файлtrace
.Почему не рекомендуется использовать ftrace на продакшен-серверах?
Трассировка создает нагрузку на CPU и может нарушить работу системы.
Примечание: Для углубленного анализа рекомендуется использовать графический интерфейс KernelShark, который визуализирует данные ftrace.
Лабораторная работа: Применение ltrace для анализа вызовов библиотечных функций
Цель работы
Изучить принципы работы с инструментом ltrace для трассировки вызовов библиотечных функций в пользовательских приложениях, освоить методы анализа взаимодействия программ с динамическими библиотеками.
Оборудование и программное обеспечение
- Операционная система: Linux (Ubuntu/Debian рекомендуется)
- Утилиты:
ltrace
(установка:sudo apt install ltrace
),gcc
,ldd
- Тестовые программы: стандартные утилиты (
ls
,cat
), простые C-программы - Права: обычный пользователь (root не требуется)
Теоретическая часть
Что такое ltrace?
ltrace — инструмент для динамического анализа пользовательских приложений, отслеживающий:
- Вызовы функций из динамических библиотек (например,
libc.so
,libm.so
) - Параметры передаваемые в функции
- Возвращаемые значения
- Время выполнения операций
Отличия от strace/ftrace
Инструмент | Уровень анализа | Пример использования |
---|---|---|
ltrace | Пользовательский (библиотечные вызовы) | ltrace ls |
strace | Системные вызовы ядра | strace ls |
ftrace | Функции ядра Linux | Через debugfs |
Как это работает?
- ltrace использует механизм ptrace для приостановки процесса.
- Перехватывает вызовы через PLT (Procedure Linkage Table).
- Анализирует символы в разделяемых библиотеках (требует наличия отладочной информации).
Основные возможности
- Отображение аргументов функций (например,
printf("Hello")
) - Измерение времени выполнения вызовов
- Фильтрация по библиотекам/функциям
- Трассировка дочерних процессов
Ход работы
Подготовка окружения
Установка ltrace:
bashsudo apt update && sudo apt install ltrace -y
Проверка поддержки динамической линковки:
bashldd /bin/ls | grep libc # Должно отобразить: libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
Задание 1: Базовая трассировка
Анализ стандартной утилиты:
bashltrace ls
Пример вывода:
__libc_start_main(0x55a3b3a0a5d0, 1, 0x7ffc1a1b3a88, 0x55a3b3a0a6c0 <unfinished ...> setlocale(6, "") = "en_US.UTF-8" bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale" textdomain("coreutils") = "coreutils" isatty(1) = 1 getenv("QUOTING_STYLE") = NULL
Анализ:
- Видны вызовы из
libc
(setlocale
,getenv
) - Показаны аргументы и возвращаемые значения
- Не отображаются системные вызовы (в отличие от strace)
- Видны вызовы из
Задание 2: Расширенная трассировка с опциями
Показ системных вызовов и библиотечных:
bashltrace -S ls 2>&1 | grep open
Вывод:
open("/etc/ld.so.cache", 577, 0666) = 3 open("/lib/x86_64-linux-gnu/libc.so.6", 577, 0666) = 3
Измерение времени выполнения:
bashltrace -t -c ls
Статистика:
% time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 32.12 0.000123 41 3 __strcmp_ssse3 28.10 0.000107 53 2 malloc 15.64 0.000060 20 3 free
Задание 3: Фильтрация вызовов
Трассировка только функций libc:
bashltrace -e "*@libc.so*" ls
Анализ конкретной функции (например,
malloc
):bashltrace -e malloc ls
Вывод:
malloc(8) = 0x564b6d5e8260 malloc(1024) = 0x564b6d5e8280 malloc(128) = 0x564b6d5e8690
Задание 4: Анализ пользовательской программы
Создание тестовой программы:
c// trace_test.c #include <stdio.h> #include <stdlib.h> int main() { char *buf = malloc(100); printf("Buffer: %p\n", buf); free(buf); return 0; }
Компиляция:
gcc trace_test.c -o trace_test
Трассировка с детализацией:
bashltrace -o trace.log ./trace_test
Содержимое trace.log:
__libc_start_main(0x401136, 1, 0x7ffd9c8a7a28, 0x4011c0 <unfinished ...> malloc(100) = 0x55f51e0d9260 printf("Buffer: %p\n", 0x55f51e0d9260Buffer: 0x55f51e0d9260 ) = 21 free(0x55f51e0d9260) = <void>
Задание 5: Сравнение с strace
Запуск параллельной трассировки:
bash(strace -e open ls 2>&1) | grep open (ltrace -S ls 2>&1) | grep open
Различия в выводе:
- strace: показывает сырые системные вызовы
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
- ltrace: декодирует вызовы через библиотеки
open("/etc/ld.so.cache", 577, 0666) = 3
- strace: показывает сырые системные вызовы
Анализ результатов
Ключевые наблюдения:
- ltrace декодирует символы библиотек, делая вывод человекочитаемым
- Показывает контекст вызова (аргументы, возвратные значения)
- Не требует прав root (в отличие от ftrace)
- Не отслеживает статически скомпилированные программы
Ограничения:
- Не работает с программами без отладочной информации
- Не показывает внутренние вызовы внутри библиотек
- Может искажать временные характеристики из-за накладных расходов
Возможные ошибки и решения
Проблема | Причина | Решение |
---|---|---|
Нет вывода при трассировке | Программа статически скомпилирована | Проверьте через ldd |
"No symbols found" | Отсутствуют debug-символы | Установите libc6-dbg |
Трассировка завершается мгновенно | Нет вызовов библиотечных функций | Используйте -S для системных вызовов |
Выводы
- ltrace — незаменимый инструмент для анализа взаимодействия приложений с библиотеками.
- Позволяет:
- Отладить проблемы с динамической линковкой
- Оптимизировать использование памяти (анализ
malloc
/free
) - Изучить скрытые зависимости программы
- Комбинирование с strace дает полную картину работы приложения (пользовательский + системный уровень).
- Критичен для анализа проприетарного ПО без исходных кодов.
Контрольные вопросы
Почему ltrace не показывает вызовы в статически скомпилированных программах?
Потому что статическая линковка включает код библиотек напрямую в бинарный файл, без использования PLT.Как определить, какие библиотеки использует программа?
Черезldd <программа>
илиltrace -l <программа>
.Чем отличается вывод
ltrace -S
от обычного strace?
ltrace декодирует параметры системных вызовов через символы библиотек, strace показывает "сырые" аргументы.Как проанализировать утечку памяти с помощью ltrace?
Трассируйте вызовыmalloc
/calloc
иfree
, проверяя баланс выделения и освобождения памяти.
Дополнительное задание
- Скомпилируйте программу с оптимизацией (
gcc -O2
):bashgcc -O2 trace_test.c -o trace_test_opt
- Сравните вывод ltrace для обычной и оптимизированной версий.
Наблюдение: Оптимизатор может устранить некоторые вызовы (например,malloc
), что отразится в трассировке.
Примечание: Для продвинутого анализа рекомендуется комбинировать ltrace с gdb (через ltrace -p <PID>
) и использовать ltrace.conf для кастомизации вывода.