О чем эта статья
Это продолжения моей вчерашней статьи об отладке приложений для Android без исходного кода на Java (если кто-то её не читал — я очень советую начать с неё). Вчера я давал пошаговую инструкцию как настроить и начать использовать связку Apk-tool плюс NetBeans. Два последних пункта там звучали примерно так:
13. Установите breakpoint на интересующую вас инструкцию… blah-blah-blah...
14. Сделайте что-нибудь в приложении, что бы ваша breakpoint сработала. После этого вы сможете делать пошаговую отладку, просматривать значения полей и переменных и т.д.
Дальше, в разделе «Подводные камни», я рассказывал почему мы не может начать отладку приложения с самого начала, например поставив breakpoint на какую-нибудь инструкцию метода
onCreate(...) в activity, с которой начинает выполняться приложение.В этой статье я расскажу как всё же можно начать отлаживать приложение без исходного кода на Java с самого начала. Эта статья опять-таки не для новичков. Нужно как минимум понимать синтаксис ассемблера Smali и уметь ручками патчить .smali файлы, грамотно вписывая туда свой код.
Инструменты
Нам снова нужны Apk-tool 1.4.1 и NetBeans 6.8 — причем именно эти устаревшие на сегодняшний день версий. С более новыми версиями заставить работать отладку у меня не получается. И судя по дискуссиям на тематических форумах — не только у меня.
Установку Apk-tool и NetBeans я уже описывал во вчерашней статье, но всё же повторюсь. NetBeans устанавливается по умолчанию, просто кликаем Next-Next-Next в мастере установки. Установка Apk-tools заключается в обычном извлечении файла
apktool.jar из архива в любую папку.Как поставить breakpoint в самом начале приложения
Идея в общем-то простая. Нужно найти activity, которая стартует в приложении первой, и вписать бесконечный цикл в начало метода
onCreate(...) этой activity. Приложение стартует и сразу после вызова конструктора этой activity будет вызван метод onCreate(...). В результате управление попадёт в наш бесконечный цикл. Пока цикл будет там крутиться, мы неспеша присоединим отладчик к работающему приложению, поставим breakpoint сразу после нашего бесконечного цикла, а потом воспользуемся возможностями отладчика и сделаем так что бы управление из этого цикла вышло. И сразу же попало на наш breakpoint. Как видим, всё элементарно.В этом разделе дана пошаговая инструкция. Инструкция написана для Windows, но скорее всего будет работать на Linux и Mac OS.
Пожалуйста в точности следуйте инструкции — это важно!
- Декодируйте ваш .apk файл в директорию
tempс помощью Apk-tool. Не используйте опцию-d:
java -jar apktool.jar d my.app.apk temp
В результате в директорииtemp/smaliу вас будет куча .smali файлов.
- В файле
temp/AndroidManifest.xmlнайдите activity с
<intent -filter="-filter"> <action android:name="android.intent.action.MAIN"> <category android:name="android.intent.category.LAUNCHER"> </intent>
Это и есть activity которая стартует в приложении первой.
- Нашли activity которая стартует в приложении первой? Теперь найдите .smali файл, в котором реализован класс для этой activity (как правило это потомок класса
android.app.Activity). Ищите в глубине директорииtemp/smali. - Теперь найдите в этом классе метод
onCreate(...)и сразу после вызова (обычно этот вызов идёт в самом начале)
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
вonCreate(...)впишите следующий код:
:debug sget v0, Lmy/activity/class/MyActivity;->debugFlag:I if-nez v0, :debug
Внимательный читатель вероятно уже догадался, что этот код и есть тот бесконечный цикл о котором мы говорили раньше. Естественно, вместоMyActivityдолжно использоваться реальное имя activity, а вместоv0в этом коде можно использовать любой подходящий локальный регистр. Если подходящего регистра нет — добавьте его, отредактировав директивы.localsи/или.registersсоответствующим образом.
- Также добавьте в класс поле
.field static debugFlag:I = 0x01
иначе код бесконечного цикла в предыдущем пункте не заработает.
- Пересоберите директорию
tempобратно в ваш .apk файл, опять-таки без опции-d:
java -jar apktool.jar b temp my.app.apk
Конечно, оригинальныйmy.app.apkстоит где-нибудь перед этим сохранить.
Теперь у нас есть пропатченый
my.app.apk. В начало метода onCreate(...) в классе той activity, с которой начинается выполнение приложения, мы вписали бесконечный цикл. Что ж, берите этот пропатченый my.app.apk и следуйте пошаговой инструкции из моей вчерашней статьи (см. раздел «Отладка»). Учтите, что на девятом шаге этой инструкции, после того как вы запустите приложение, вы увидите черный экран. Это нормально, так и должно быть! Это просто означает, что сразу после запуска приложения был вызван наш пропатченый метод onCreate(...) и управление попало в наш бесконечный цикл. Если через некоторое время Android предложит вам закрыть приложение потому что оно не отвечает — откажитесь и идите дальше строго по инструкции!На двенадцатом шаге инструкции откройте в NetBeans тот .java файл, в котором находится пропатченный вами метод
onCreate(...). Воспользуйтесь кнопкой «пауза» на панели отладки в NetBeans. Затем в этом открытом .java файле поставьте breakpoint на первую инструкцию после кода бесконечного цикла, который вы вписали в onCreate(...). Потом, пользуясь функцией просмотра и редактирования переменных в отладчике NetBeans, поменяйте значение поля debugFlag на 0 и кликните на кнопку «продолжить отладку» на панели отладки в NetBeans. Управление выйдет из бесконечного цикла и тут же попадёт на ваш breakpoint.Всё, теперь можно спокойно отлаживать приложение фактически с самого начала — с первого вызова самого первого метода
onCreate(...)!Пару слов про waitForDebugger()
Читатель, который немного в теме, наверное читал на тематических форумах про использование метода
android.os.Debug.waitForDebugger() для тех же целей, для которых мы в этой статье используем бесконечный цикл. И этот самый читатель вероятно удивлён, что мы тут нагородили какой-то огород с циклом, хотя можно было бы просто вписать в начало нашего onCreate(...) вызов всего одного статического метода:invoke-static {}, Landroid/os/Debug;->waitForDebugger()VЗаметьте, метод вызывается без параметров, а значит не нужно мучиться с добавлением локальных регистров если нету подходящего. Казалось бы — красота! Чего ещё надо?
В теории — ничего не надо, бери да пользуйся. Но на практике всё чуток сложнее. По факту фокус с
android.os.Debug.waitForDebugger() работает далеко не всегда и не у всех. У многих (в том числе и у меня) сразу после вызова android.os.Debug.waitForDebugger() приложение действительно «замирает» и ждёт когда к нему присоединится отладчик. Это видно даже в DDMS — напротив приложения появляется маленький значок «красный жук». Но как только мы присоединяем отладчик к приложению, управление тут же переходит на следующую инструкцию после android.os.Debug.waitForDebugger() и приложение начинает выполняться дальше без остановки. Мы просто не успеваем поставить breakpoint после android.os.Debug.waitForDebugger(). Обсуждение по этому поводу см. например тут.Почему
android.os.Debug.waitForDebugger() у кого-то работает так, а у кого-то эдак — мне пока что неизвестно. Может в комментариях кто-то даст пояснения по этому поводу. Также в комментариях можно и нужно задавать вопросы по статье. Постараюсь ответить по-возможности оперативно, но если буду тупить — пожалуйста наберитесь терпения. Постараюсь ответить всем.Happy debugging!