KasperskyOS Community Edition 1.3

Использование динамических библиотек

В решении на базе KasperskyOS можно использовать динамические библиотеки (файлы *.so). По сравнению со статическими библиотеками (файлами *.a) динамические библиотеки дают следующие преимущества:

  • Экономия оперативной памяти.

    Несколько процессов могут использовать один экземпляр динамической библиотеки. Также исполняемый файл и динамические библиотеки в одном процессе могут использовать один экземпляр динамической библиотеки.

    Динамические библиотеки могут загружаться в память и выгружаться из нее по инициативе программ, которые их используют.

  • Удобство обновления ПО.

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

  • Возможность реализовать механизм плагинов.

    Плагины для компонентов решения представляют собой динамические библиотеки.

  • Совместное использование кода и данных.

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

Динамические библиотеки поставляются в составе KasperskyOS Community Edition, а также могут быть созданы разработчиком решения на базе KasperskyOS. Работоспособность сторонних динамических библиотек не гарантируется.

В настоящее время из-за технических ограничений в решении на базе KasperskyOS нельзя использовать libc.so и libpthread.so.

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

Условия, необходимые для использования динамических библиотек

Жизненный цикл динамической библиотеки

Включение системной программы BlobContainer в решение на базе KasperskyOS

Сборка динамических библиотек

Добавление динамических библиотек в решение на базе KasperskyOS

В начало
[Topic shared_libraries]

Условия, необходимые для использования динамических библиотек

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

  1. Процессы, использующие динамические библиотеки, должны иметь доступ к файловым системам, в которых хранятся файлы динамических библиотек. Доступ к файловым системам обеспечивается VFS, которая является отдельным процессом. VFS и другое ПО, с помощью которого VFS работает с накопителем (например драйвер накопителя), не должны использовать динамические библиотеки.
  2. Системная программа BlobContainer должна быть включена в решение.
  3. Исполняемые файлы, использующие динамические библиотеки, должны быть собраны с флагом -rdynamic (с динамической компоновкой).

    CMake-команда initialize_platform() делает так, что для сборки всех исполняемых файлов, заданных через CMake-команды add_executable(), этот флаг используется автоматически.

Если CMake-команда initialize_platform(FORCE_STATIC) указана в корневом файле CMakeLists.txt, то тулчейн выполняет статическую компоновку исполняемых файлов.

CMake-команда project_static_executable_header_default() влияет на сборку исполняемых файлов, заданных через последующие CMake-команды add_executable() в одном файле CMakeLists.txt. Тулчейн выполняет статическую компоновку этих исполняемых файлов.

CMake-команда platform_target_force_static() влияет на сборку одного исполняемого файла, заданного через CMake-команду add_executable(). Тулчейн выполняет статическую компоновку этого исполняемого файла.

Исполняемый файл, который собирается с флагом -rdynamic, компонуется со статической библиотекой, если динамическая библиотека не найдена. Например, если используется CMake-команда target_link_libraries(client -lm), исполняемый файл программы client компонуется со статической библиотекой libm.a, если динамическая библиотека libm.so не найдена.

В начало
[Topic shared_libraries_use_conditions]

Жизненный цикл динамической библиотеки

Жизненный цикл динамической библиотеки включает следующие стадии:

  1. Загрузка в память.

    Динамическая библиотека, скомпонованная с исполняемым файлом, загружается в разделяемую память при запуске процесса, созданного на основе этого исполняемого файла. Также процесс может загрузить динамическую библиотеку в разделяемую память вызовом функции dlopen() интерфейса POSIX. Динамическая библиотека может быть скомпонована с другими динамическими библиотеками, поэтому исполняемый файл зависит не только от непосредственно скомпонованной с ним динамической библиотеки, но и от всего графа зависимостей этой библиотеки. Динамическая библиотека загружается в разделяемую память совместно с всеми динамическими библиотеками, от которых зависит.

    Один экземпляр динамической библиотеки загружается в разделяемую память независимо от того, сколько процессов использует эту библиотеку. (Точнее, в разделяемую память загружается только часть динамической библиотеки, включающая код и доступные только на чтение данные. Другая часть динамической библиотеки загружается в память каждого процесса, который использует эту библиотеку.) Динамическая библиотека, от которой зависит нескольких других динамических библиотек, загружается в разделяемую память в единственном экземпляре.

    Если задать через переменную окружения LD_PRELOAD список динамических библиотек, то эти динамические библиотеки будут загружены в разделяемую память, даже если исполняемый файл не зависит от них. (Элементами списка должны быть абсолютные или относительные пути к динамическим библиотекам, разделенные двоеточием, например: LD_PRELOAD=libmalloc.so:libfree.so:/usr/somepath/lib/libfoo.so.) Функции, которые экспортируются динамическими библиотеками, указанными в LD_PRELOAD, замещают одноименные функции, которые экспортируются другими динамическими библиотеками. Это можно использовать для целей отладки, если требуется подменить функции, импортируемые из динамических библиотек.

    Загрузчик динамических библиотек выполняет поиск динамических библиотек в следующем порядке:

    1. По абсолютным путям, заданным через переменную окружения LD_LIBRARY_PATH.

      Пути должны быть разделены двоеточием, например: LD_LIBRARY_PATH=/usr/lib:/home/user/lib.

    2. По абсолютным путям, заданным в поле DT_RUNPATH или DT_RPATH секции .dynamic исполняемых файлов и динамических библиотек.

      При компоновке исполняемых файлов и динамических библиотек могут быть заданы пути, по которым загрузчик динамических библиотек будет выполнять поиск. (Это можно сделать, например, через свойство INSTALL_RPATH в CMake-команде set_target_properties().) Пути для поиска динамических библиотек сохраняются в поле DT_RUNPATH или DT_RPATH секции .dynamic. Это поле может быть как в исполняемых файлах, скомпонованных с динамическими библиотеки, так и в самих динамических библиотеках, скомпонованных с другими динамическими библиотеками.

    3. По пути /lib.

    Загрузчик динамических библиотек выполняет поиск в том же порядке, если в параметре filename функции dlopen() или в переменной окружения LD_PRELOAD указан относительный путь к динамической библиотеке. Если указан абсолютный путь, то загрузчик помещает динамическую библиотеку в разделяемую память без выполнения поиска.

  2. Использование процессом (процессами).
  3. Выгрузка из памяти.

    Динамическая библиотека выгружается из разделяемой памяти, когда все процессы, использующие эту библиотеку, завершились или вызвали функцию dlclose() интерфейса POSIX. Динамическая библиотека, скомпонованная с исполняемым файлом, не может быть выгружена из разделяемой памяти до завершения процесса, который был создан на основе этого исполняемого файла. Динамическая библиотека, скомпонованная с другими динамическими библиотеками, выгружается из разделяемой памяти после выгрузки всех библиотек, которые зависят от нее.

В начало
[Topic shared_libraries_lifecycle]

Включение системной программы BlobContainer в решение на базе KasperskyOS

Если в решении используются динамические библиотеки, то в это решение должна быть включена системная программ BlobContainer (исполняемый файл sysroot-aarch64-kos/bin/BlobContainer из состава KasperskyOS Community Edition).

Программа BlobContainer может быть включена в решение автоматически или вручную. Автоматическое включение этой программы в решение выполняется CMake-командами build_kos_qemu_image() и build_kos_hw_image(), если как минимум один исполняемый файл в решении скомпонован с динамической библиотекой. (Чтобы отключить автоматическое включение программы BlobContainer в решение, нужно добавить значение NO_AUTO_BLOB_CONTAINER в параметры CMake-команд build_kos_qemu_image() и build_kos_hw_image().) Если программы в решении работают с динамическими библиотеками, используя только интерфейс POSIX (функции dlopen(), dlsym(), dlerror(), dlclose()), то программу BlobContainer нужно включить в решение вручную.

VFS, работающая с файлами динамических библиотек, должна быть отдельным процессом. Нужно создать IPC-канал от процесса программы BlobContainer к процессу VFS.

Должны быть созданы IPC-каналы от процессов, использующих динамические библиотеки, к процессу программы BlobContainer. Эти IPC-каналы могут быть созданы как статически, так и динамически. Если статически созданный IPC-канал отсутствует, клиентская и серверная части программы BlobContainer пытаются создать IPC-канал динамически, используя сервер имен.

Если программа BlobContainer включена в решение автоматически, то макросы @INIT_EXTERNAL_ENTITIES@, @INIT_<имя программы>_ENTITY_CONNECTIONS@ и @INIT_<имя программы>_ENTITY_CONNECTIONS+@, используемые в файле init.yaml.in, автоматически создают в init-описании словари IPC-каналов, которые обеспечивают статическое создание IPC-каналов от процессов, созданных на основе исполняемых файлов, скомпонованных с динамическими библиотеками, к процессу программы BlobContainer. (Процесс программы BlobContainer получает имя kl.bc.BlobContainer, а IPC-каналы получают имя kl.BlobContainer.) При этом для процессов, которые работают с динамическими библиотеками, используя только интерфейс POSIX, словари IPC-каналов к процессу программы BlobContainer автоматически не создаются, и, чтобы требуемые IPC-каналы были созданы статически, нужно создать эти словари вручную (эти IPC-каналы должны иметь имя kl.BlobContainer).

Если программа BlobContainer включена в решение вручную, и требуется статически создать IPC-каналы от процессов, использующих динамические библиотеки, к процессу программы BlobContainer, то нужно вручную создать словари необходимых IPC-каналов в init-описании. По умолчанию IPC-канал к процессу программы BlobContainer имеет имя kl.BlobContainer, но это имя можно изменить через переменную окружения _BLOB_CONTAINER_BACKEND. Эту переменную нужно задать как для процесса BlobContainer, так и для процессов, использующих динамические библиотеки.

Переменная окружения _BLOB_CONTAINER_BACKEND задает не только имя статически создаваемых IPC-каналов к процессу программы BlobContainer, но и имя службы, публикуемое на сервере имен, которое используется для динамического создания IPC-каналов к процессу программы BlobContainer. Это удобно использовать, когда запущено одновременно несколько процессов программы BlobContainer (например, с целью изоляции собственных динамических библиотек от сторонних), и разные процессы, использующие динамические библиотеки, должны взаимодействовать через IPC с разными процессами программы BlobContainer. В таком случае для разных процессов программы BlobContainer нужно задать разные значения переменной окружения _BLOB_CONTAINER_BACKEND, а затем использовать эти значения для переменной окружения _BLOB_CONTAINER_BACKEND процессов, использующих динамические библиотеки, выбирая конкретное значение в зависимости того, с каким именно процессом программы BlobContainer требуется динамически создать IPC-канал.

Пример использования переменной окружения _BLOB_CONTAINER_BACKEND в файле init.yaml.in:

entities: - name: example.BlobContainer path: example_blob_container args: - "-v" env: _BLOB_CONTAINER_BACKEND: kl.custombc @INIT_example_blob_container_ENTITY_CONNECTIONS@ - name: client.Client path: client env: _BLOB_CONTAINER_BACKEND: kl.custombc @INIT_client_ENTITY_CONNECTIONS@ @INIT_EXTERNAL_ENTITIES@

Пример использования переменной окружения _BLOB_CONTAINER_BACKEND в CMake-командах:

set_target_properties (ExecMgrEntity PROPERTIES EXTRA_ENV " _BLOB_CONTAINER_BACKEND: kl.custombc") set_target_properties (dump_collector::entity PROPERTIES EXTRA_ENV " _BLOB_CONTAINER_BACKEND: kl.custombc")
В начало
[Topic shared_libraries_blobcontainer]

Сборка динамических библиотек

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

add_library(<имя цели сборки> SHARED <путь к файлу исходного кода библиотеки>...)

Также можно выполнить сборку динамической библиотеки следующей CMake-командой:

add_library(<имя цели сборки> <путь к файлу исходного кода библиотеки>...)

При этом shell-команду cmake нужно вызвать с параметром -D BUILD_SHARED_LIBS=YES. (Если вызвать shell-команду cmake без параметра -D BUILD_SHARED_LIBS=YES, будет выполнена сборка статической библиотеки.)

Пример:

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

По умолчанию имя файла библиотеки совпадает с именем цели сборки, заданным через параметр CMake-команды add_library(). Имя файла библиотеки можно изменить, используя CMake-команду set_target_properties(). Это можно использовать, чтобы имя файла библиотеки было одинаковым для ее динамического и статического варианта.

Пример:

# Сборка статической библиотеки add_library(somelib_static STATIC src/somesrc.cpp) set_target_properties(somelib_static PROPERTIES OUTPUT_NAME "somelib") # Сборка динамической библиотеки add_library(somelib_shared SHARED src/somesrc.cpp) set_target_properties(somelib_shared PROPERTIES OUTPUT_NAME "somelib")

Динамическая библиотека может быть скомпонована с другими статическими и динамическими библиотеками CMake-командой target_link_libraries(). При этом статические библиотеки должны быть собраны с флагом -fPIC. Этот флаг применяется при сборке статической библиотеки, если используется следующая CMake-команда:

set_property(TARGET <имя цели сборки>... PROPERTY POSITION_INDEPENDENT_CODE ON)
В начало
[Topic shared_libraries_building]

Добавление динамических библиотек в решение на базе KasperskyOS

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

Способ с автоматическим поиском динамических библиотек

Нужно использовать параметры PACK_DEPS_COPY_ONLY ON, PACK_DEPS_LIBS_PATH и PACK_DEPS_COPY_TARGET в CMake-командах build_kos_qemu_image() и build_kos_hw_image().

Динамические библиотеки, которые загружаются в память вызовом функции dlopen() интерфейса POSIX, не будут найдены автоматическим поиском.

Пример 1:

# В этом примере создается образ решения для аппаратной платформы. # В этот образ решения добавляется образ накопителя с динамическими # библиотеками. # Установка значений переменных set (IMAGE_FS ${CMAKE_BINARY_DIR}/hdd) set (LIBS_PATH "${IMAGE_FS}/lib") set (DISK_IMG ramdisk0.img) if (blob_container_ENTITY_FOUND) # Настройка программы BlobContainer set_target_properties (${blob_container_ENTITY} PROPERTIES DEPENDS_ON_ENTITY ${precompiled_vfsVfsRamFs} EXTRA_ENV " VFS_FILESYSTEM_BACKEND: client:kl.VfsRamFs") endif () # Создание образа накопителя с динамическими библиотеками # Образ накопителя будет создан после выполнения цели copylibs, # которая создается CMake-командой build_kos_hw_image(). add_custom_command (OUTPUT ${DISK_IMG} DEPENDS copylibs COMMAND ${KL_SDK_ROOT_PATH}/common/prepare_hdd_img.sh -d ${IMAGE_FS} -s 64 -f ext4 -img ${DISK_IMG} COMMENT "Creating disk image '${DISK_IMG}' from files in '${IMAGE_FS}' ...") # Создание образа решения для аппаратной платформы # Цель copylibs означает копирование автоматически найденных динамических # библиотек в директорию ${LIBS_PATH}. Эта директория будет включена # в образ накопителя. Поскольку значение ${DISK_IMG} указано в параметре # IMAGE_FILES, образ накопителя будет создан перед созданием образа решения, # а также будет включен в образ решения. build_kos_hw_image (kos-image ... IMAGE_FILES ${ENTITIES_LIST} ${DISK_IMG} PACK_DEPS_COPY_ONLY ON PACK_DEPS_LIBS_PATH ${LIBS_PATH} PACK_DEPS_COPY_TARGET copylibs)

Пример 2:

# В этом примере для запуска на QEMU создаются образ решения и # отдельный образ накопителя с динамическими библиотеками, # а для запуска на аппаратной платформе создается образ SD-карты, # в который добавляются образ решения и динамические библиотеки. # Установка значений переменных, используемых при сборке # для аппаратной платформы set (HW_IMAGE_FS "${CMAKE_BINARY_DIR}/hdd_hw") set (HW_LIBS_PATH "${HW_IMAGE_FS}/lib") # Установка значений переменных, используемых при сборке # для QEMU set (QEMU_IMAGE_FS "${CMAKE_BINARY_DIR}/hdd_qemu") set (QEMU_LIBS_PATH "${QEMU_IMAGE_FS}/lib") set (QEMU_DISK_IMAGE sdcard0.img) set (QEMU_FLAGS "-nic none -m 2048 -drive file=${QEMU_DISK_IMAGE},if=sd,format=raw") set (QEMU_DEPENDENCIES ${QEMU_DISK_IMAGE}) if (blob_container_ENTITY_FOUND) # Настройка программы BlobContainer set_target_properties (${blob_container_ENTITY} PROPERTIES DEPENDS_ON_ENTITY ${precompiled_vfsVfsSdCardFs} EXTRA_ENV " VFS_FILESYSTEM_BACKEND: client:kl.VfsSdCardFs") # Создание образа накопителя с динамическими библиотеками для QEMU add_custom_command (${QEMU_DISK_IMAGE} DEPENDS copylibs_qemu COMMAND ${KL_SDK_ROOT_PATH}/common/prepare_hdd_img.sh -d ${QEMU_IMAGE_FS} -s 64 -f fat32 -img ${QEMU_DISK_IMAGE} COMMENT "Creating disk image '${QEMU_DISK_IMAGE}' from files in '${QEMU_IMAGE_FS}' ...") # Создание образа решения для QEMU # Цель copylibs_qemu означает копирование автоматически найденных динамических # библиотек в директорию ${QEMU_LIBS_PATH}. Эта директория будет включена # в образ накопителя с динамическими библиотеками для QEMU. Значение ${QEMU_FLAGS} # содержит параметры -drive file=${QEMU_DISK_IMAGE},if=sd,format=raw, которые # требуются для использования на QEMU образа накопителя с динамическими # библиотеками. Значение ${QEMU_DEPENDENCIES} требуется, чтобы обеспечить # создание образа накопителя с динамическими библиотеками до создания образа # решения для QEMU. (Образ решения не будет включать образ накопителя с # динамическими библиотеками, но добавление этой зависимости целей # нужно, чтобы выполнить создание образа накопителя с динамическими библиотеками.) build_kos_qemu_image (kos-qemu-image ... QEMU_FLAGS "${QEMU_FLAGS}" QEMU_DEPENDENCIES "${QEMU_DEPENDENCIES}" PACK_DEPS_COPY_ONLY ON PACK_DEPS_LIBS_PATH "${QEMU_LIBS_PATH}" PACK_DEPS_COPY_TARGET copylibs_qemu IMAGE_FILES ${ENTITIES}) # Создание образа решения для аппаратной платформы # Цель copylibs_hw означает копирование автоматически найденных динамических # библиотек в директорию ${HW_LIBS_PATH}. Эта директория будет включена # в образ SD-карты, который создается CMake-командой build_sd_image(). build_kos_hw_image (kos-image ... PACK_DEPS_COPY_ONLY ON PACK_DEPS_LIBS_PATH "${HW_LIBS_PATH}" PACK_DEPS_COPY_TARGET copylibs_hw IMAGE_FILES ${ENTITIES}) # Образ решения для аппаратной платформы будет создан после копирования # автоматически найденных динамических библиотек в директорию ${HW_LIBS_PATH}. # (Образ решения не будет включать динамические библиотеки, но добавление # этой зависимости целей нужно, чтобы выполнить копирование до создания # образа SD-карты.) add_dependencies (kos-image copylibs_hw) # Создание образа SD-карты # Образ SD-карты будет создан на основе директории ${HW_IMAGE_FS}, # включающей образ решения для аппаратной платформы и директорию # с динамическими библиотеками. Образ SD-карты будет создан # после создания образа решения для аппаратной платформы. build_sd_image (sd-image KOS_IMAGE_TARGET kos-image IMAGE_FS ${HW_IMAGE_FS})

Способ с ручным поиском динамических библиотек

Нужно самостоятельно выполнить поиск динамических библиотек, от которых зависят программы решения. Это позволяет добавить в решение даже те динамические библиотеки, которые загружаются в память вызовом функции dlopen() интерфейса POSIX.

Пример 1:

# В этом примере создается образ решения, включающий, помимо # исполняемых файлов программ, динамическую библиотеку libm.so. # Настройка программы VFS set (VFS_SDCARD_ARGS "\ - -l - nodev /tmp ramfs 0 - -l - devfs /dev devfs 0 - -l - romfs /lib romfs ro") set_target_properties (${precompiled_vfsVfsSdCardFs} PROPERTIES EXTRA_ARGS ${VFS_SDCARD_ARGS}) if (blob_container_ENTITY_FOUND) # Настройка программы BlobContainer set_target_properties (${blob_container_ENTITY} PROPERTIES DEPENDS_ON_ENTITY ${precompiled_vfsVfsSdCardFs} EXTRA_ENV " VFS_FILESYSTEM_BACKEND: client:kl.VfsSdCardFs") endif () if(PLATFORM_SUPPORTS_DYNAMIC_LINKING) # Получение полного пути к динамической библиотеке libm.so find_file(LIBM_SO_FILE libm.so PATH_SUFFIXES lib REQUIRED) set(EXTRA_FILES ${LIBM_SO_FILE}) else() set(EXTRA_FILES) endif() # Создание образа решения для аппаратной платформы # Динамическая библиотека libm.so будет добавлена в # образ решения, поскольку значение ${EXTRA_FILES}, # указанное в параметре IMAGE_FILES, содержит полный # путь к этой библиотеке. build_kos_hw_image(kos-image ... IMAGE_FILES ${ENTITIES} ${EXTRA_FILES})

Пример 2:

# В этом примере для запуска на QEMU создаются образ решения и # отдельный образ накопителя с динамической библиотекой libm.so. # Установка значений переменных set (QEMU_FLAGS "-nic none -m 2048") set (QEMU_DEPENDENCIES) if (PLATFORM_SUPPORTS_DYNAMIC_LINKING) # Установка значений переменных set (IMAGE_FS "${CMAKE_BINARY_DIR}/hdd") set (LIBS_PATH "${IMAGE_FS}/lib") set (QEMU_DISK_IMAGE sdcard0.img) string (APPEND QEMU_FLAGS "-drive file=${QEMU_DISK_IMAGE},if=sd,format=raw") set (QEMU_DEPENDENCIES ${QEMU_DISK_IMAGE}) # Настройка программы BlobContainer set_target_properties (${blob_container_ENTITY} PROPERTIES DEPENDS_ON_ENTITY ${precompiled_vfsVfsSdCardFs} EXTRA_ENV " VFS_FILESYSTEM_BACKEND: client:kl.VfsSdCardFs") # Получение полного пути к динамической библиотеке libm.so find_file (LIBM_SO_FILE libm.so PATH_SUFFIXES lib REQUIRED) # Создание образа накопителя с динамической библиотекой libm.so add_custom_command (OUTPUT ${QEMU_DISK_IMAGE} COMMAND ${CMAKE_COMMAND} -E make_directory ${LIBS_PATH} COMMAND ${CMAKE_COMMAND} -E copy ${LIBM_SO_FILE} ${LIBS_PATH} COMMAND ${KL_SDK_ROOT_PATH}/common/prepare_hdd_img.sh -d ${IMAGE_FS} -s 64 -f fat32 -img ${QEMU_DISK_IMAGE} COMMENT "Creating disk image '${QEMU_DISK_IMAGE}' from files in '${IMAGE_FS}' ...") endif () # Создание образа решения для QEMU # Значение ${QEMU_FLAGS} содержит параметры -drive file=${QEMU_DISK_IMAGE},if=sd,format=raw, # которые требуются для использования на QEMU образа накопителя с динамической # библиотекой libm.so. Значение ${QEMU_DEPENDENCIES} требуется, чтобы обеспечить # создание образа накопителя с динамической библиотекой libm.so до создания образа # решения для QEMU. (Образ решения не будет включать образ накопителя с # динамической библиотекой libm.so, но добавление этой зависимости целей # нужно, чтобы выполнить создание образа накопителя с динамической библиотекой libm.so.) build_kos_qemu_image (kos-qemu-image ... QEMU_FLAGS "${QEMU_FLAGS}" QEMU_DEPENDENCIES "${QEMU_DEPENDENCIES}" IMAGE_FILES ${ENTITIES})
В начало
[Topic shared_libraries_adding_to_solution]