Введение
Доброго времени суток!
Однажды передо мною встала задача отловить непонятное падение в моем приложении. Насколько я знал тогда, Android NDK предоставлял возможность отлаживать C++ код, однако я смутно представлял себе, как это сделать. К сожалению, толковой информации по отладке нативного кода было очень мало. Потратив на это дело несколько вечеров, я все-таки разобрался и наладил отладку. Сейчас я расскажу о том, как это можно сделать и расскажу о том, какие грабли могут ожидать вас, если вы задумаете повторить мой путь.
Для удобства я создал проект приложения для отладки — NdkDebugTest. Приложение показывает черный экран, а при нажатии на экран вызывается нативный код, который убивает приложение. Проект состоит из двух java-файлов и одного jni-файла. Это, соответственно, код Activity, код для JNI-оболочки и нативный код.
Код Activity довольно простой — при любом прикосновении вызывается нативная функция OnInput. Также при создании Activity вызывается пустой метод DoInit(), чья задача — запустить статическую загрузку нативных библиотек из JniWrapper.java (если этого не сделать, мы попадем на грабли, см. ниже).
Вот код:
package fishrungames.ndkdebugtest;
//...
public class MainActivity extends Activity
{
protected void onCreate(Bundle icicle)
{
super.onCreate(icicle);
JniWrapper.DoInit();
}
public boolean onTouchEvent(MotionEvent event)
{
float x = event.getX();
float y = event.getY();
JniWrapper.OnInput(x, y);
return true;
}
}
Оболочка Jni тоже довольно простая:
package fishrungames.ndkdebugtest;
public class JniWrapper
{
static {
System.loadLibrary("gnustl_shared");
System.loadLibrary("NdkDebugTestLib");
}
public static void DoInit()
{
//To force libraries to load
}
public static native void OnInput(float x, float y);
}
Ну и собственно нативный код. Здесь из Jni-функции вызывается другая функция, которая и убивает приложение:
#include "android_api.h"
void crusher()
{
int *x = 0;
*x = 1;
}
JNIEXPORT void JNICALL Java_fishrungames_ndkdebugtest_JniWrapper_OnInput(JNIEnv * env, jobject obj, float x, float y)
{
crusher();
}
Как я и писал выше, после запуска приложения при любом нажатии пальцем приложение падает. Наша задача — найти место падения и посмотреть backtrace. Итак, приступим.
Инструменты
У нас в распоряжении должны быть:
- Eclipse с открытым проектом NdkBuildTest
- Cygwin
- Установленный android-sdk
- Установленный android-ndk, путь к которому должен быть указан в переменной окружения PATH. Чем старше версия ndk, тем лучше (я использовал android-ndk-r8)
Подготовка
1) Убедитесь, что в манифесте установлен параметр debuggable=«true»:
<application android:icon="@drawable/ic_menu_template" android:label=«NdkDebugTest» android:debuggable=«true»>
2) В Application.mk допишите следующую строку:
APP_OPTIM := debug
Этот параметр, если я не ошибаюсь, отменияет stripping (удаление неиспользуемых символов) при копировании библиотек, и делает еще какие-то другие полезные для отладки вещи.
В Android.mk в LOCAL_CFLAGS следует добавить параметры -g -ggdb -O0, а из параметров LOCAL_LDLIBS следует убрать -s и -S (если есть).
3) После этого запускаем Cygwin, отправляется в каталог с проектом и запускаем cборку с ключом NDK_DEBUG=1:
ndk-build NDK_DEBUG=1
Если все сделано правильно, то в каталоге проекта в подкаталоге libs/<платформа>/ появятся файлы gdb.setup и gdbserver.
4) В gdb.setup указываются базовые настройки дебаггера — в каких каталогах искать заголовки и библиотеки. Надо проверить, все ли пути к заголовкам указаны и, при необходимости, дописать свои пути.
Здесь лежат грабли. Почему-то при каждой пересборке в этом файле сбиваются символы переноса строк, из-за чего настройки не считываются. Следует убедится, что в файле применен UNIX-перенос строк. Мне приходилось после каждого вызова ndk-build пересохранять gdb.setup с другим переносом строк!
При запуске отладчика этот файл (gdb.setup) автоматически дополняется другими параметрами и копируется в obj/local/<платформа>/. Все нативные библиотеки, необходимые для работы приложения, также хранятся в obj/local/<платформа>/. Если сборка прошла верно, то библиотеки содержат в себе дебажные символы. Если вы в этом сомневаетесь, то наличие или отсутствие дебажных символов возможно проверить прямо из консоли Cygwin с помощью утилиты nm, указав в качестве параметра искомый so-файл:
nm /obj/local/armeabi/libNdkDebugLib.so
В некоторых старых версиях android-ndk также рекомендовалось отредактировать файл build/core/build-binary.mk, а именно: поменять строчку:
$(hide) $(call cmd-strip, $(PRIVATE_DST))
На строчки:
ifneq ($(APP_OPTIM),debug)
$(hide) $(call cmd-strip, $(PRIVATE_DST))
endif
Отладка
Все готово к отладке. Запускаем отладку через eclipse, дожидаемся, пока отладчик подцепится. Затем в cygwin переходим в каталог с проектом и запускаем нативный отладчик ndk-gdb. В параметр --adb указывается путь к adb, который находится в составе android-sdk:
Здесь лежат еще одни грабли. Убедитесь, что все нативные библиотеки (в нашем случае — NdkDebugTestLib) загружены до запуска! Проверить это можно, поставив брейкпоинт на загрузке библиотеки.
Если все сделано правильно, перед вами появится длинный список библиотек, для которых символы не были загружены. Убедитесь, что среди них нету вашей библиотеки (в нашем случае — libNdkDebugTestLib.so).
Если все пройдет хорошо, мы увидим приглашение к вводу:
Продолжим выполнение командой continue. Затем нажимаем пальцем на экран девайса, и приложение падает, указав точку падения:
Падение произошло при вызове *x = 1; в строке 6 в файле jni/android_api.cpp, как и ожидалось.
Вводим backtrace — и видим стек вызовов функций, которые привели к падению:
Заключение
Что же, цель достигнута — мы нашли место падения приложения, и увидели стек вызовов. Используя отладчик, можно ставить брейкпойнты, вычислять выражения и делать многое другое, но найти информацию о том как использовать отладчик командной строки проблем не составит.
Вот ссылка на проект, который описан в статье: http://fishrungames.ru/4habr/NdkDebugTest.rar.
Источники
http://vilimpoc.org/blog/2010/09/23/hello-gdbserver-a-debuggable-jni-example-for-android/
http://blog.sephiroth.it/2010/12/14/how-to-debug-native-code-with-android/
http://mhandroid.wordpress.com/2011/01/23/using-eclipse-for-android-cc-debugging/
Автор: Mephi1984
При запуске gdb можно указать –start, в таком случае eclipse не нужен
Спасибо, за статью, ваш проект дебажится. мой пока не хочет :(