
После перевода наших проектов на 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 не сесть в лужу.
Демо-проект находится здесь.
Для успешной сборки и настройки отладчика, вам понадобятся:
- Android SDK
- Android NDK r8+
- CMake 2.8.11+
- MinGW (если вы на Windows)
- Переменная окружения ANDROID_NDK, указывающая на распакованный NDK
После загрузки демо-проекта, запустите
cmake.cmd/sh
, после чего в Eclipse импортируйте проекты из папок build/android_debug
и sources/Teapot
.Для проекта
TeapotNativeActivity
можно добавить референс на TeapotNativeActivity-Debug@android_debug
, чтобы пересборка происходила автоматически.В итоге у вас должен получиться такой workspace:

Немного теории
Отладка native кода Android-приложения включает в себя несколько этапов:
- Сборка нативных библиотек с отладочными символами (чтобы отладчик мог связать код в исходниках и скомпилированный код).
- Создание урезанных версий библиотек без отладочных символов, но со ссылкой на них.
- Сборка и установка APK на устройство с урезанными версиями библиотек.
- Запуск APK.
- Поиск идентификатора процесса (PID) запущенного APK.
- Запуск удалённого отладчика на устройстве с указанием PID. Отладчик открывает TCP порт на устройстве, куда можно слать команды gdb.
- Пробрасывание TCP порта с устройства на локальный компьютер.
- Загрузка с устройства системных файлов, необходимых для корректной работы отладчика (установки правильных адресов и смещений).
- Запуск отладчика на локальном компьютере с указанием локального TCP порта и конфигурацией путей поиска библиотек и исходников.
В Eclipse всё это разделяется на несколько основных этапов:
- Сборка нативного кода.
- Сборка APK.
- Запуск APK.
- Выполнение скрипта-хелпера для настройки отладчика локально и удалённо.
- Запуск отладчика нативного кода.
Наша задача — объединить это всё в один клик.
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.
В его задачи входит:
- Проверка наличия флага
android:debuggable="true"
в AndroidManifest.xml
.
- Получение идентификатора package из
AndroidManifest.xml
.
- Подключение к девайсу и поиск PID запущенного инстанса по идентификатору package.
- Запуск
gdbserver
, пробрасывание порта.
- Копирование дополнительных файлов для отладчика на локальную машину.
- Создание файла
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, которые уже позволяют запустить отладку, но для этого нужно:
- Запустить APK
- Запустить скрипт ndk-gdb.py
- Запустить отладчик
Мы же рассчитываем отлаживать наше приложение в один клик, так?
Launch Group to the rescue!
На наше счастье, Eclipse CDT предлагает механизм по автоматизации нескольких разных действий в Eclipse, и имя ему Launch Group.
Создайте конфигурацию с последовательносью:
- Запуск отладки APK, задержка 2 секунды. Задержка нужна для стабильного запуска APK, т.к. следующий шаг требует запущенный APK, иначе скрипт не найдёт нужный процесс.
- Запуск скрипта ndk-gdb.py, задержка 2 секунды. Задержка нужна для стабильного пробрасывания порта с устройства.
- Запуск отладчика 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 автоматически, потому как каждый новый проект/ветка требует такой настройки.
Если у вас появились вопросы или предложения по улучшению/упрощению схемы, я буду рад их услышать!