KasperskyOS Community Edition 1.3

Отладка программ с использованием GDB-сервера ядра KasperskyOS

GDB-сервер ядра имеет следующие особенности:

  • Распознает процессы и потоки исполнения, управляемые KasperskyOS.
  • Поддерживает отладку на аппаратных платформах с SMP (в том числе при эмуляции на QEMU).

В этом разделе

Подготовка к отладке на QEMU

Начальные шаги отладки на QEMU

Подготовка к отладке на аппаратной платформе

Начальные шаги отладки на аппаратной платформе

Работа с процессами и потоками исполнения

В начало
[Topic debugging_kernelstub]

Подготовка к отладке на QEMU

Чтобы подготовить условия для отладки с использованием GDB-сервера ядра KasperskyOS на QEMU, нужно выполнить следующие шаги:

  1. Проверить, что в составе KasperskyOS Community Edition есть версия ядра с GDB-сервером.

    Для это нужно убедиться в наличии файла libexec/aarch64-kos/kos-qemu-gdbstub в составе KasperskyOS Community Edition.

  2. Добавить параметр GDBSTUB_KERNEL в список параметров CMake-команды build_kos_qemu_image().

    Этот параметр включает в решение ядро с GDB-сервером.

    Пример:

    build_kos_qemu_image(kos-qemu-image GDBSTUB_KERNEL ... QEMU_FLAGS "${QEMU_FLAGS}")
  3. Создать файл .gdbinit, чтобы не вызывать вручную начальные GDB-команды при каждом запуске отладчика GDB. (Файл .gdbinit нужно сохранить в директории, где будет запускаться отладчик GDB.)

    Пример:

    # Задать путь для поиска динамических библиотек с отладочными символами из # состава KasperskyOS Community Edition set sysroot /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos # Задать дополнительные пути для поиска динамических библиотек с отладочными # символами set solib-search-path /home/user/example/build/lib1:/home/user/example/build/lib2 # Настроить управление потоками исполнения (опционально) # Отладчик GDB может работать в режиме all-stop или non-stop. В первом режиме # отладчик GDB считает, что при остановке потока исполнения, контекст которого # находится в фокусе отладки, останавливаются все остальные потоки исполнения, # о которых известно отладчику GDB. Во втором режиме отладчик GDB считает, что # все потоки, кроме остановленного, продолжают исполняться. Режим all-stop # применяется отладчиком GDB по умолчанию, и GDB-сервер ядра дает возможность # использовать только этот режим. Чтобы изменить заданные по умолчанию параметры # управления потоками исполнения в режиме all-stop, нужно использовать GDB-команды # set scheduler-locking и set schedule-multiple. # В этом примере первая GDB-команда указывает отладчику GDB, что при возобновлении # исполнения потока, контекст которого находится в фокусе отладки (например, # GDB-командами continue, step, next), нужно возобновить исполнение остальных потоков. # Вторая GDB-команда указывает отладчику GDB, что при возобновлении исполнения потока, # контекст которого находится в фокусе отладки, нужно возобновить исполнение всех # потоков, входящих в процессы всех программ, для которых выполняется отладка. set scheduler-locking off set schedule-multiple on # Подключить отладчик GDB к GDB-серверу ядра # GDB-сервер ядра взаимодействует с отладчиком GDB по расширенному протоколу, # и QEMU предоставляет доступ к GDB-серверу ядра через TCP-сокет. target extended-remote localhost:1234
В начало
[Topic debugging_kernelstub_qemu_preparing]

Начальные шаги отладки на QEMU

Чтобы начать отладку с использованием GDB-сервера ядра KasperskyOS на QEMU, нужно выполнить следующие шаги:

  1. Собрать отладочные версии исполняемых файлов и библиотек, создать образ решения для QEMU и запустить QEMU.

    Для этого нужно вызвать shell-команды cmake, указав параметры -D CMAKE_BUILD_TYPE:STRING=Debug и --target sim.

    Пример:

    #!/bin/bash ... cmake -G "Unix Makefiles" \ -D CMAKE_BUILD_TYPE:STRING=Debug \ -D CMAKE_TOOLCHAIN_FILE=$SDK_PREFIX/toolchain/share/toolchain-$TARGET.cmake \ -B build \ && cmake --build build --target sim

    Вместо цели sim можно указать цель sim/fast, чтобы не выполнять повторную сборку.

    QEMU запускается и исполняет код решения. Исполнение кода решения останавливается при подключении отладчика GDB.

  2. Запустить отладчик GDB и подключиться к GDB-серверу ядра.

    Для этого нужно запустить исполняемый файл toolchain/bin/aarch64-kos-gdb из состава KasperskyOS Community Edition. Запуск нужно выполнить в директории, где сохранен созданный вручную файл .gdbinit.

  3. Создать инфериоры, привязать их к процессам и загрузить отладочные символы.

    Чтобы загрузить отладочные символы, нужно переключиться на инфериор, ассоциированный с процессом, и использовать следующие GDB-команды:

    • add-symbol-file <путь к файлу> – для исполняемых файлов со статической или динамической компоновкой;
    • file <путь к файлу> – для исполняемых файлов с динамической компоновкой.

    GDB-команду file нужно использовать, чтобы отладчик GDB загрузил отладочные символы исполняемого файла и динамических библиотек, от которых зависит этот исполняемый файл. Чтобы отладчик GDB загрузил отладочные символы только исполняемого файла с динамической компоновкой, нужно использовать GDB-команду add-symbol-file.

    Если отладочные символы сохраняются не в исполняемых файлах, а в отдельных файлах, то в исполняемые файлы добавляются ссылки на файлы с отладочными символами. При вызове GDB-команды add-symbol-file или file можно указать как исполняемый файл, так и файл с отладочными символами.

При повторной сборке (шаг 1) нужно завершить сессию отладки (выйти из отладчика GDB) и выполнить повторно шаги 2 и 3.

В начало
[Topic debugging_kernelstub_qemu_initsteps]

Подготовка к отладке на аппаратной платформе

Чтобы подготовить условия для отладки с использованием GDB-сервера ядра KasperskyOS на аппаратной платформе, нужно выполнить следующие шаги:

  1. Проверить, что в составе KasperskyOS Community Edition есть версии ядра с GDB-сервером.

    Для это нужно убедиться в наличии файла libexec/aarch64-kos/kos-gdbstub в составе KasperskyOS Community Edition.

  2. Скоммутировать компьютер, на котором будет работать отладчик GDB, с аппаратной платформой.

    Для подключения компьютера к аппаратной платформе нужно использовать преобразователи USB-UART. Схемы соединения преобразователей USB-UART и аппаратных платформ см. в "Подготовка Raspberry Pi 4 B к запуску примеров" и "Подготовка Radxa ROCK 3A к запуску примеров".

  3. Добавить параметр GDBSTUB_KERNEL в список параметров CMake-команды build_kos_hw_image().

    Этот параметр включает в решение ядро с GDB-сервером.

    Пример:

    build_kos_hw_image(kos-image GDBSTUB_KERNEL ... IMAGE_FILES ${ENTITIES})
  4. Создать файл .gdbinit, чтобы не вызывать вручную начальные GDB-команды при каждом запуске отладчика GDB. (Файл .gdbinit нужно сохранить в директории, где будет запускаться отладчик GDB.)

    Пример:

    # Задать путь для поиска динамических библиотек с отладочными символами из # состава KasperskyOS Community Edition set sysroot /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos # Задать дополнительные пути для поиска динамических библиотек с отладочными # символами set solib-search-path /home/user/example/build/lib1:/home/user/example/build/lib2 # Настроить управление потоками исполнения (опционально) # Отладчик GDB может работать в режиме all-stop или non-stop. В первом режиме # отладчик GDB считает, что при остановке потока исполнения, контекст которого # находится в фокусе отладки, останавливаются все остальные потоки исполнения, # о которых известно отладчику GDB. Во втором режиме отладчик GDB считает, что # все потоки, кроме остановленного, продолжают исполняться. Режим all-stop # применяется отладчиком GDB по умолчанию, и GDB-сервер ядра дает возможность # использовать только этот режим. Чтобы изменить заданные по умолчанию параметры # управления потоками исполнения в режиме all-stop, нужно использовать GDB-команды # set scheduler-locking и set schedule-multiple. # В этом примере первая GDB-команда указывает отладчику GDB, что при возобновлении # исполнения потока, контекст которого находится в фокусе отладки (например, # GDB-командами continue, step, next), нужно возобновить исполнение остальных потоков. # Вторая GDB-команда указывает отладчику GDB, что при возобновлении исполнения потока, # контекст которого находится в фокусе отладки, нужно возобновить исполнение всех # потоков, входящих в процессы всех программ, для которых выполняется отладка. set scheduler-locking off set schedule-multiple on # Задать символьную скорость для взаимодействия с GDB-сервером ядра # Нужно задать значение 115200 бод. set serial baud 115200 # Подключить отладчик GDB к GDB-серверу ядра # Нужно задать порт USB, через который подключен преобразователь USB-UART, # используемый для отладки. Также необходимо задать, что GDB-сервер ядра # взаимодействует с отладчиком GDB по расширенному протоколу. target extended-remote /dev/ttyUSB1
В начало
[Topic debugging_kernelstub_hw_preparing]

Начальные шаги отладки на аппаратной платформе

Чтобы начать отладку с использованием GDB-сервера ядра KasperskyOS на аппаратной платформе, нужно выполнить следующие шаги:

  1. Собрать отладочные версии исполняемых файлов и библиотек и создать образ решения для аппаратной платформы.

    Для этого нужно вызвать shell-команды cmake, указав параметры -D CMAKE_BUILD_TYPE:STRING=Debug и --target <tgt>, где tgt – значение параметра NAME в CMake-команде build_kos_hw_image().

    Пример:

    #!/bin/bash ... cmake -G "Unix Makefiles" \ -D CMAKE_BUILD_TYPE:STRING=Debug \ -D CMAKE_TOOLCHAIN_FILE=$SDK_PREFIX/toolchain/share/toolchain-$TARGET.cmake \ -B build \ && cmake --build build --target kos-image
  2. Загрузить образ решения на аппаратную платформу и запустить решение.

    Исполнение кода решения останавливается при подключении отладчика GDB.

  3. Запустить отладчик GDB и подключиться к GDB-серверу ядра.

    Для этого нужно запустить исполняемый файл toolchain/bin/aarch64-kos-gdb из состава KasperskyOS Community Edition. Запуск нужно выполнить в директории, где сохранен созданный вручную файл .gdbinit.

  4. Создать инфериоры, привязать их к процессами и загрузить отладочные символы.

    Чтобы загрузить отладочные символы, нужно переключиться на инфериор, ассоциированный с процессом, и использовать следующие GDB-команды:

    • add-symbol-file <путь к файлу> – для исполняемых файлов со статической или динамической компоновкой;
    • file <путь к файлу> – для исполняемых файлов с динамической компоновкой.

    GDB-команду file нужно использовать, чтобы отладчик GDB загрузил отладочные символы исполняемого файла и динамических библиотек, от которых зависит этот исполняемый файл. Чтобы отладчик GDB загрузил отладочные символы только исполняемого файла с динамической компоновкой, нужно использовать GDB-команду add-symbol-file.

    Если отладочные символы сохраняются не в исполняемых файлах, а в отдельных файлах, то в исполняемые файлы добавляются ссылки на файлы с отладочными символами. При вызове GDB-команды add-symbol-file или file можно указывать как исполняемый файл, так и файл с отладочными символами.

При повторной сборке (шаг 1) нужно завершить сессию отладки (выйти из отладчика GDB) и выполнить повторно шаги 2-4.

В начало
[Topic debugging_kernelstub_hw_initsteps]

Работа с процессами и потоками исполнения

В отладчике GDB для работы с процессами используются объекты, которые называются инфериорами (англ. inferiors). Процесс каждой программы, которую нужно отладить, должен быть привязан к отдельному инфериору. Переключение между инфериорами соответствует переключению фокуса отладки в контекст другого процесса. Отладочные символы для программы, которую требуется отладить, нужно загружать после переключения на инфериор, который привязан к процессу этой программы.

При запуске отладчика GDB автоматически создается инфериор. При подключении отладчика GDB к GDB-серверу ядра исполнение кода решения останавливается, и этот инфериор автоматически привязывается к процессу, содержащему поток исполнения, в контексте которого останавливается исполнение кода решения. Остальные требуемые инфериоры могут быть созданы, привязаны к процессам и отвязаны от процессов вручную или автоматически. В первом случае нужно использовать GDB-команды для работы с инфериорами. Во втором случае требуется настроить обработку событий жизненного цикла процессов.

GDB-команды для работы с инфериорами

Для работы с инфериорами нужно использовать следующие GDB-команды:

  • info inferiors

    Выводит список инфериоров. Текущий инфериор отмечается символом *. (Контекст процесса, ассоциированного с текущим инфериором, находится в фокусе отладки.)

  • add-inferior

    Создает инфериор.

  • inferior <номер инфериора>

    Переключает на другой инфериор.

  • attach <PID>

    Привязывает текущий инфериор к процессу с идентификатором PID. Чтобы вывести список процессов, нужно выполнить GDB-команду info os processes.

  • detach

    Отвязывает текущий инфериор от процесса.

Настройка обработки событий жизненного цикла процессов

GDB-сервер ядра сообщает отладчику GDB о наступлении следующих событий:

  1. создание дочернего процесса;
  2. запуск начального потока исполнения дочернего процесса;
  3. завершение процесса.

Отладчик GDB обрабатывает событие 1 только в том случае, если существует инфериор, привязанный к родительскому процессу. Для обработки события 2 необходимо, чтобы отладчик GDB предварительно обработал событие 1 при создании дочернего процесса. При этом событие 2 обрабатывается, даже если родительский процесс уже отвязан от инфериора, например, по причине завершения.

Чтобы настроить обработку событий 1 и 2, нужно использовать следующие GDB-команды:

  • set follow-fork-mode {child|parent}

    Указывает отладчику GDB перейти в контекст дочернего процесса (child) или остаться в контексте родительского процесса (parent) при наступлении события 1. Второй вариант выполняется по умолчанию.

  • set detach-on-fork {on|off}

    Указывает отладчику GDB оставить родительский или дочерний процесс привязанным к инфериору (on) или оставить родительский процесс привязанным к инфериору, а также создать новый инфериор и привязать его к дочернему процессу (off) при наступлении события 1. Первый вариант выполняется по умолчанию. При этом, какой процесс привязан к инфериору (родительский или дочерний), зависит от параметра GDB-команды set follow-fork-mode.

  • set follow-exec-mode {new|same}

    Указывает отладчику GDB создать новый инфериор и привязать его к дочернему процессу (new) или не делать этого (same) при наступлении события 2. Второй вариант выполняется по умолчанию.

Рекомендуется настроить обработку событий 1 и 2 следующим образом:

set follow-fork-mode parent set follow-exec-mode same set detach-on-fork off

Такая конфигурация обеспечивает обработку событий 1 и 2 отладчиком GDB следующим образом:

  • При наступлении события 1 создается новый инфериор, который привязывается к дочернему процессу.
  • При наступлении события 1 родительский процесс остается привязанным к инфериору.
  • При наступлении события 2 не создается новый инфериор для дочернего процесса.

Обработку отладчиком GDB события 2 можно использовать для остановки исполнения программы до передачи управления функции main(), чтобы не применять бесконечный цикл.

Чтобы исполнение программы остановилось при наступлении события 2, нужно использовать следующую GDB-команду:

catch exec

Эта GDB-команда обеспечивает остановку исполнения каждого дочернего процесса, при создании которого было обработано событие 1. Чтобы выполнить остановку исполнения отдельных процессов, нужно использовать следующую GDB-команду:

monitor exec [<имя процесса 1>][, <имя процесса 2>]...

Последующий вызов этой GDB-команды отменяет предыдущий, а ее вызов без параметров означает остановку каждого дочернего процесса, при создании которого было обработано событие 1.

Событие 3 обрабатывается отладчиком GDB только в том случае, если созданы инфериоры, привязанные к завершившимся процессам. При обработке этого события отладчик GDB отвязывает инфериор от завершившегося процесса. Обработку события 3 не требуется настраивать.

GDB-команды для работы с потоками исполнения

Чтобы вывести список потоков исполнения, которые содержатся в процессах, привязанных к инфериорам, нужно выполнить следующую GDB-команду:

info threads

Текущий поток исполнения отмечается символом *. (Контекст текущего потока исполнения находится в фокусе отладки.)

Чтобы перейти в контекст другого потока исполнения, нужно выполнить следующую GDB-команду:

thread <значение Id, полученное вызовом GDB-команды info threads>

Переход в контекст другого потока исполнения приводит к переключению на другой инфериор, если этот поток исполнения содержится процессе, привязанном к инфериору, который не является текущим.

В начало
[Topic debugging_kernelstub_processes_threads]