Skip to content

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 — включение/отключение записи трассировки.

Ход работы

Подготовка окружения

  1. Проверка доступности debugfs:

    bash
    ls /sys/kernel/debug/tracing

    Если каталог отсутствует, смонтируйте debugfs:

    bash
    sudo mount -t debugfs nodev /sys/kernel/debug
  2. Проверка поддержки ftrace в ядре: Убедитесь, что в конфигурации ядра включены опции:

    bash
    grep CONFIG_FUNCTION_TRACER /boot/config-$(uname -r)
    # Должно вернуть: CONFIG_FUNCTION_TRACER=y

Задание 1: Базовая трассировка функций

  1. Выбор трейсера:

    bash
    echo function > /sys/kernel/debug/tracing/current_tracer
  2. Очистка предыдущих данных:

    bash
    echo > /sys/kernel/debug/tracing/trace
  3. Включение трассировки:

    bash
    echo 1 > /sys/kernel/debug/tracing/tracing_on
  4. Выполнение тестовой операции (например, запуск ls):

    bash
    ls / > /dev/null
  5. Остановка трассировки:

    bash
    echo 0 > /sys/kernel/debug/tracing/tracing_on
  6. Просмотр результатов:

    bash
    cat /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: Фильтрация функций

  1. Трассировка конкретной функции (например, do_sys_open):

    bash
    echo 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
  2. Анализ вывода: Вывод будет содержать только вызовы do_sys_open и связанные с ней функции.


Задание 3: Граф вызовов (function_graph)

  1. Включение graph-трейсера:

    bash
    echo 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
  2. Выполнение операции (например, чтение файла):

    bash
    cat /proc/cpuinfo > /dev/null
  3. Просмотр дерева вызовов:

    bash
    cat /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: Сбор статистики

  1. Включение статистического трейсера:

    bash
    echo 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
  2. Просмотр статистики:

    bash
    cat /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

Анализ результатов

  1. Function tracer позволяет увидеть последовательность вызовов, но не показывает вложенность.
  2. Function graph демонстрирует иерархию вызовов и время выполнения, что критично для поиска узких мест.
  3. Фильтрация через set_ftrace_filter снижает объем данных и упрощает анализ.
  4. Статистика (trace_stat) помогает выявить "тяжелые" функции по времени выполнения.

Возможные ошибки и решения

ПроблемаРешение
Каталог /sys/kernel/debug/tracing отсутствуетВыполните mount -t debugfs nodev /sys/kernel/debug
Нет записи в trace после операцииПроверьте tracing_on (должен быть 1), убедитесь, что трейсер активен
Ошибка Operation not permittedЗапустите команды от root (sudo -i)

Выводы

  1. ftrace — мощный инструмент для анализа внутренней работы ядра Linux.
  2. Базовые трейсеры (function, function_graph) позволяют отслеживать вызовы функций и выявлять проблемы производительности.
  3. Фильтрация и ограничение глубины трассировки критичны для работы с большими объемами данных.
  4. Инструмент требует осторожности: длительная трассировка может замедлить систему.

Контрольные вопросы

  1. Какие трейсеры доступны в вашей системе?
    Проверьте файл /sys/kernel/debug/tracing/available_tracers.

  2. Как остановить трассировку без потери данных?
    Запишите 0 в tracing_on, затем прочитайте файл trace.

  3. Почему не рекомендуется использовать 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

Как это работает?

  1. ltrace использует механизм ptrace для приостановки процесса.
  2. Перехватывает вызовы через PLT (Procedure Linkage Table).
  3. Анализирует символы в разделяемых библиотеках (требует наличия отладочной информации).

Основные возможности

  • Отображение аргументов функций (например, printf("Hello"))
  • Измерение времени выполнения вызовов
  • Фильтрация по библиотекам/функциям
  • Трассировка дочерних процессов

Ход работы

Подготовка окружения

  1. Установка ltrace:

    bash
    sudo apt update && sudo apt install ltrace -y
  2. Проверка поддержки динамической линковки:

    bash
    ldd /bin/ls | grep libc
    # Должно отобразить: libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6

Задание 1: Базовая трассировка

  1. Анализ стандартной утилиты:

    bash
    ltrace ls
  2. Пример вывода:

    __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
  3. Анализ:

    • Видны вызовы из libc (setlocale, getenv)
    • Показаны аргументы и возвращаемые значения
    • Не отображаются системные вызовы (в отличие от strace)

Задание 2: Расширенная трассировка с опциями

  1. Показ системных вызовов и библиотечных:

    bash
    ltrace -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
  2. Измерение времени выполнения:

    bash
    ltrace -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: Фильтрация вызовов

  1. Трассировка только функций libc:

    bash
    ltrace -e "*@libc.so*" ls
  2. Анализ конкретной функции (например, malloc):

    bash
    ltrace -e malloc ls

    Вывод:

    malloc(8)                                                                       = 0x564b6d5e8260
    malloc(1024)                                                                    = 0x564b6d5e8280
    malloc(128)                                                                     = 0x564b6d5e8690

Задание 4: Анализ пользовательской программы

  1. Создание тестовой программы:

    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

  2. Трассировка с детализацией:

    bash
    ltrace -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

  1. Запуск параллельной трассировки:

    bash
    (strace -e open ls 2>&1) | grep open
    (ltrace -S ls 2>&1) | grep open
  2. Различия в выводе:

    • strace: показывает сырые системные вызовы
      openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    • ltrace: декодирует вызовы через библиотеки
      open("/etc/ld.so.cache", 577, 0666) = 3

Анализ результатов

  1. Ключевые наблюдения:

    • ltrace декодирует символы библиотек, делая вывод человекочитаемым
    • Показывает контекст вызова (аргументы, возвратные значения)
    • Не требует прав root (в отличие от ftrace)
    • Не отслеживает статически скомпилированные программы
  2. Ограничения:

    • Не работает с программами без отладочной информации
    • Не показывает внутренние вызовы внутри библиотек
    • Может искажать временные характеристики из-за накладных расходов

Возможные ошибки и решения

ПроблемаПричинаРешение
Нет вывода при трассировкеПрограмма статически скомпилированаПроверьте через ldd
"No symbols found"Отсутствуют debug-символыУстановите libc6-dbg
Трассировка завершается мгновенноНет вызовов библиотечных функцийИспользуйте -S для системных вызовов

Выводы

  1. ltrace — незаменимый инструмент для анализа взаимодействия приложений с библиотеками.
  2. Позволяет:
    • Отладить проблемы с динамической линковкой
    • Оптимизировать использование памяти (анализ malloc/free)
    • Изучить скрытые зависимости программы
  3. Комбинирование с strace дает полную картину работы приложения (пользовательский + системный уровень).
  4. Критичен для анализа проприетарного ПО без исходных кодов.

Контрольные вопросы

  1. Почему ltrace не показывает вызовы в статически скомпилированных программах?
    Потому что статическая линковка включает код библиотек напрямую в бинарный файл, без использования PLT.

  2. Как определить, какие библиотеки использует программа?
    Через ldd <программа> или ltrace -l <программа>.

  3. Чем отличается вывод ltrace -S от обычного strace?
    ltrace декодирует параметры системных вызовов через символы библиотек, strace показывает "сырые" аргументы.

  4. Как проанализировать утечку памяти с помощью ltrace?
    Трассируйте вызовы malloc/calloc и free, проверяя баланс выделения и освобождения памяти.


Дополнительное задание

  1. Скомпилируйте программу с оптимизацией (gcc -O2):
    bash
    gcc -O2 trace_test.c -o trace_test_opt
  2. Сравните вывод ltrace для обычной и оптимизированной версий.
    Наблюдение: Оптимизатор может устранить некоторые вызовы (например, malloc), что отразится в трассировке.

Примечание: Для продвинутого анализа рекомендуется комбинировать ltrace с gdb (через ltrace -p <PID>) и использовать ltrace.conf для кастомизации вывода.

Контакты: bystrovno@basealt.ru