Отладка Android CMake проекта по-взрослому


    После перевода наших проектов на CMake, встал вопрос об отладке нативной части Android. Так как инструменты NDK стали неактуальны, пришлось залезть в дебри и научить Eclipse запускать удалённую отладку CDT-проекта, что называется, вручную. В идеале, чтобы это выполнялось в один клик.

    Если вам не чужда разработка с использованием Android NDK, и вы хотите познать некоторые тонкости отладки, велкам под кат.

    В одной из наших студий было принято решение о переводе кроссплатформенных проектов на CMake, что позволило ускорить их разработку, но также внесло свои коррективы в некоторые этапы. CMake полностью заменил систему сборки ndk-build, и «инструменты отладки» ndk-stack, ndk-gdb стали недоступны. Помедитировав над Eclipse и CDT в частности, появилась мысль о том, что отладку нативного кода CMake Android-проекта можно сделать удобной, быстрой и доступной.

    CMake Android-проект


    В этой статье мы рассмотрим нашу задачу в слегка уменьшенном масштабе — на примере Teapot из состава Android NDK. Его сборка была переведена с ndk-build скриптов на CMake. Надо пояснить, что CMake не заточен полностью под Android, как и под любую другую платформу. Дополнительный функционал нужно писать самому, что и было сделано в данном случае, поэтому не удивляйтесь, если видите какую-то переменную или функцию, о которых в документации CMake ни слова. Бóльшую часть я попытаюсь описать, но кое-что могу упустить, поэтому если вы решите перенять опыт, то вам придётся вникнуть в используемые скрипты и хотя бы примерно понимать их работу, дабы при выходе новой версии NDK не сесть в лужу.

    Демо-проект находится здесь.

    Для успешной сборки и настройки отладчика, вам понадобятся:


    После загрузки демо-проекта, запустите cmake.cmd/sh, после чего в Eclipse импортируйте проекты из папок build/android_debug и sources/Teapot.
    Для проекта TeapotNativeActivity можно добавить референс на TeapotNativeActivity-Debug@android_debug, чтобы пересборка происходила автоматически.

    В итоге у вас должен получиться такой workspace:



    Немного теории


    Отладка native кода Android-приложения включает в себя несколько этапов:

    1. Сборка нативных библиотек с отладочными символами (чтобы отладчик мог связать код в исходниках и скомпилированный код).
    2. Создание урезанных версий библиотек без отладочных символов, но со ссылкой на них.
    3. Сборка и установка APK на устройство с урезанными версиями библиотек.
    4. Запуск APK.
    5. Поиск идентификатора процесса (PID) запущенного APK.
    6. Запуск удалённого отладчика на устройстве с указанием PID. Отладчик открывает TCP порт на устройстве, куда можно слать команды gdb.
    7. Пробрасывание TCP порта с устройства на локальный компьютер.
    8. Загрузка с устройства системных файлов, необходимых для корректной работы отладчика (установки правильных адресов и смещений).
    9. Запуск отладчика на локальном компьютере с указанием локального TCP порта и конфигурацией путей поиска библиотек и исходников.

    В Eclipse всё это разделяется на несколько основных этапов:

    1. Сборка нативного кода.
    2. Сборка APK.
    3. Запуск APK.
    4. Выполнение скрипта-хелпера для настройки отладчика локально и удалённо.
    5. Запуск отладчика нативного кода.

    Наша задача — объединить это всё в один клик.

    Challenge accepted


    Сборка .so

    Сборка отладочных версий библиотек не представляет сложностей (CMake всё делает за нас), однако нюанс поджидает в совместимости форматов отладочной информации. Мы используем для сборки своих проектов arm тулчейн gcc-4.8, в котором почему-то используется gdb, не совместимый из коробки с генерируемым форматом dwarf. Для нахождения общего языка, компилятору нужно указать конкретный формат dwarf.

    Для этого прописываем флаги в CMake:

    if (PLATFORM_ANDROID)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -gdwarf-2")
      set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -gdwarf-2")
    endif()
    


    Помещение отладочной информации в отдельный файл

    Размер библиотеки с отладочными данными может достигать нескольких сотен мегабайт, что негативно влияет на размер APK и, как следствие, скорость загрузки на устройство. Для решения этой проблемы выполняем strip отладочной информации в отдельный файл:

    # Separate debug information and runnable binary
    add_custom_command(TARGET ${target} POST_BUILD
    	# Rename .so file to .so.debug file
    	COMMAND ${CMAKE_COMMAND} -E rename $<TARGET_FILE:${target}> $<TARGET_FILE:${target}>.debug
    	# Strip .so.debug file to only runnable part and put it into .so file
    	COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded $<TARGET_FILE:${target}>.debug -o $<TARGET_FILE:${target}>
    	# Add link to debuggable .so.debug file to .so file
    	COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink=$<TARGET_FILE:${target}>.debug $<TARGET_FILE:${target}>)
    

    После сборки, полная версия .so переименовывается в .so.debug, и на её основе создаётся .so с убранной информацией об отладке и ссылкой на .so.debug файл.
    Этот код находится в функции set_ndk_gdbserver_compatible(target) в файле CMake/auto_included/android_helpers.cmake. Помимо выполнения стрипа после сборки, он также выполняет другие важные шаги, о которых ниже.

    Удаленный отладчик a.k.a. gdbserver

    Удаленный отладчик gdbserver по умолчанию отсутствует на устройстве, но поставляется с Android NDK в папке <android-ndk>/prebuilt/android-arm/gdbserver. Для того чтобы поместить его на устройство, используется небольшой трюк: gdbserver копируется в папку <android_project>/libs/ Android-проекта и попадает вместе с библиотеками в папку /data/<package_name>/lib.

    Копирование gdbserver в папку <android_project>/libs/ выполняется автоматически при сборке проекта. Это происходит в set_ndk_gdbserver_compatible(target). Операция будет выполнена только в том случае, если используемая CMake конфигурация находится в глобальном проперти DEBUG_CONFIGURATIONS (по умолчанию туда включена Debug конфигурация, остальные добавляются через вызов set_debug_configuration(config)).

    При сборке не отладочной конфигурации файл gdbserver будет удаляться.

    Скрипт ndk-gdb.py

    Для настройки устройства и локальной машины используется переработанная версия скрипта ndk-gdb.py из Android NDK.

    В его задачи входит:

    1. Проверка наличия флага android:debuggable="true" в AndroidManifest.xml.
    2. Получение идентификатора package из AndroidManifest.xml.
    3. Подключение к девайсу и поиск PID запущенного инстанса по идентификатору package.
    4. Запуск gdbserver, пробрасывание порта.
    5. Копирование дополнительных файлов для отладчика на локальную машину.
    6. Создание файла gdb.setup для отладчика на локальной машине.

    Этот скрипт создаётся на основе шаблона CMake/auto_included/stuff/ndk-gdb.py.in в момент вызова set_ndk_gdbserver_compatible(target). В процессе запуска отладки скрипт должен запускаться после старта APK.

    Настройка Eclipse для запуска ndk-gdb.py

    Здесь и далее, параллельно описанию, будет идти мануал по настройке демо-проекта.

    Добавляем External Tool с такой конфигурацией:


    Так как это промежуточный шаг для запуска отладчика, то привязка к билду нам не нужна:


    Настройка отладчика C++ Remote Application


    Eclipse CDT предлагает несколько вариантов работы отладчика, но нас интересует единственно верный в данном случае: C++ Remote Application.

    В настройки отладчика прописываются пути:

    • приложения для отладки
    • отладчика
    • конфигурации отладчика

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

    Конфигурация отладчика - это файл gdb.setup, в котором указаны пути для поиска библиотек.

    После генерации CMake-проекта, в папке build/android_debug/ndk-gdb создаются пустышки app_process и gdb.setup, чтобы на них можно было натравить Eclipse. В момент запуска отладки, скрипт ndk-gdb.py подгружает с девайса настоящий файл app_process и конфигурирует gdb.setup.

    Настройка отладчика в Eclipse

    Что отлаживаем:


    Чем отлаживаем:


    Отладчик берётся из тулчейна Android NDK: <android_ndk>/toolchains//prebuilt//bin/arm-linux-androideabi-gdb(.exe).

    Как удобно-то!


    В результате этих действий мы получили несколько разных задач в Eclipse, которые уже позволяют запустить отладку, но для этого нужно:

    1. Запустить APK
    2. Запустить скрипт ndk-gdb.py
    3. Запустить отладчик

    Мы же рассчитываем отлаживать наше приложение в один клик, так?

    Launch Group to the rescue!


    На наше счастье, Eclipse CDT предлагает механизм по автоматизации нескольких разных действий в Eclipse, и имя ему Launch Group.

    Создайте конфигурацию с последовательносью:

    1. Запуск отладки APK, задержка 2 секунды. Задержка нужна для стабильного запуска APK, т.к. следующий шаг требует запущенный APK, иначе скрипт не найдёт нужный процесс.
    2. Запуск скрипта ndk-gdb.py, задержка 2 секунды. Задержка нужна для стабильного пробрасывания порта с устройства.
    3. Запуск отладчика C++ Remote Application.





    Для удобства на вкладке Common активируйте отображение этого шага на кнопке отладки в Eclipse:


    Debug me


    Если всё сделано правильно и у вас хорошая карма, то при запуске этого действия вы сможете наслаждаться отладкой вашего проекта:





    Что-то пошло не так?


    Проблемы бывают, и мы их периодически ловим. Разные девайсы имеют разную производительность, прошивки, версии Android.
    Чаще всего сложности возникают из-за медленного запуска APK, что приводит к подключению отладчика ещё на фазе загрузки .so, что негативно сказывается на запуске, и порой вообще невозможно продолжить работу. Для решения этой проблемы нужно увеличить задержку между первым и вторым шагом в Launch Group.
    Ещё возможна проблема с зависанием отладчика на девайсе, ребут устройства обычно спасает.

    Также в предложенном примере используется ndk-gdb.py
    скрипт, рассчитанный на то, что его будут запускать только на Linux x86, Mac OS X x64 или Windows x64. Если ваша конфигурация отличается, то вам придётся его подкрутить.

    Заключение


    Всё это позволило значительно упростить отладку любого Android CMake-проекта, и мы этому несказанно рады. Конечно, есть простор для улучшений и оптимизаций: в идеале хочется сделать нечто, что могло бы настроить все эти шаги в Eclipse автоматически, потому как каждый новый проект/ветка требует такой настройки.

    Если у вас появились вопросы или предложения по улучшению/упрощению схемы, я буду рад их услышать!
    • +26
    • 12k
    • 6
    Alawar Entertainment
    53,00
    Компания
    Поделиться публикацией

    Похожие публикации

    Комментарии 6

      +1
      Отличная статья. Актуальная.
        0
        А разве нельзя было сразу через dbg запускать — он же умеет.
          0
          тьфу — gdb :)
          0
          Отладка Android CMake проекта по-взрослому

          Детям читать опасно для психики
            +1
            Спасибо за статью, получился отличный тьюториал по настройке дебага для Android NDK. Не скажу, что уж очень много информации в сети по этому процессу, да к тому же она сильно фрагментирована. Я потратил в свое время на это достаточно сил.

            Начав читать статью, я уж думал, что вы генератор для CMake написали, который
            нечто, что могло бы настроить все эти шаги в Eclipse автоматически, потому как каждый новый проект/ветка требует такой настройки.
            но надежды не сбылись…
              0
              Для этого нужно знать Eclipse-плагин-кунг-фу, чего практически никто не знает и не хочет знать, а изучать самому ради одной задачи смысла особо нет.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое