О чем эта статья
Это продолжения моей вчерашней статьи об отладке приложений для Adnroid без исходного кода на 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
Внимательный читатель вероятно уже догадался, что этот код и есть тот бесконечный цикл о котором мы говорили раньше. Естественно, вместо
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()
у кого-то работает так, а у кого-то эдак — мне пока что неизвестно. Может в комментариях кто-то даст пояснения по этому поводу. Также в комментариях можно и нужно задавать вопросы по статье. Постараюсь ответить по-возможности оперативно, но если буду тупить — пожалуйста наберитесь терпения. Постараюсь ответить всем.
Автор: dimakovalenko