Эта рассказ о нестандартном подходе к разработке Android-приложений. Одно дело — установка Android Studio и написание «Hello, World» на Java или Kotlin. Но я покажу, как эту же задачу можно выполнить иначе.
Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».
Skillbox рекомендует: Образовательный онлайн-курс «Профессия Java-разработчик».
Как работает мой смартфон с Android OS?
Сначала небольшая предыстория. Однажды вечером мне позвонила знакомая по имени Ариэлла. Она спросила меня: «Слушай, а как работает мой смартфон? Что у него внутри? Как электрическая энергия и обычные единицы и нули позволяют всему этому функционировать?»
Моя знакомая не нуб в разработке, она создала несколько проектов на Arduino, которые состояли как из программной, так и из аппаратной частей. Может быть, именно поэтому она захотела узнать больше. Мне удалось ответить при помощи знаний, полученных на одном из курсов информатики, пройденных в университете.
Затем мы работали пару недель вместе, поскольку Ариэлла захотела узнать, как работают кирпичики электронной техники, то есть полупроводниковые элементы, включая транзисторы. Далее мы вышли на более высокий уровень: я ей показал, как можно создавать логические вентили, к примеру NAND (логическое И) плюс NOR (логическое ИЛИ) c использованием специфической комбинации транзисторов.
Мы исследовали логические элементы разных видов, объединяли их для выполнения вычислений (например, добавления двух бинарных чисел) и ячеек памяти (триггеров). Когда все прояснилось, начали разрабатывать простой процессор (воображаемый), в котором было два регистра общего назначения и две простые инструкции (добавление этих регистров). Мы даже написали простую программу, которая умножает эти два числа.
Кстати, если вас интересует эта тема, то прочитайте инструкцию по созданию 8-битного компьютера с нуля. Здесь объясняется практически все, с самых основ. Хотел бы я прочитать это раньше!
Hello, Android!
После завершения всех этапов изучения мне показалось, что у Ариэллы хватит знаний, чтобы понять, как работает процессор смартфона. Ее смартфон — Galaxy S6 Edge, база которого — архитектура ARM (как, собственно, и у большинства смартфонов). Мы решили написать «Hello, World»-приложение для Android, но на ассемблере.
.text
.globl _start
_start:
mov %r0, $1 // file descriptor number 1 (stdout)
ldr %r1, =message
mov %r2, $message_len
mov %r7, $4 // syscall 4 (write)
swi $0
mov %r0, $0 // exit status 0 (ok)
mov %r7, $1 // syscall 1 (exit)
swi $0
.data
message:
.ascii "Hello, World\n"
message_len = . - message
Если вы никогда раньше не сталкивались с кодом ассемблера, то этот блок может вас напугать. Но ничего страшного, давайте разберем код вместе.
Итак, наша программа состоит из двух частей. Первая — это текст с инструкциями машинного кода, и вторая — переменные, строки и другая информация (начиная со строки 15). Раздел .text обычно доступен лишь для чтения, а .data — также и для записи.
В строке 2 мы определяем глобальную функцию с названием _start. Она представляет собой точку входа в приложение. ОС начинает выполнять код именно с этой точки. Определение функции объявлено в строке 4.
Кроме того, функция выполняет еще две вещи. В строках 5–9 сообщение выводится на экран, в строках 11–13 программа завершается. Даже если удалить 11–13 строки, программа выведет нашу строку «Hello, World» и завершится. Тем не менее выход не будет корректным, поскольку программа завершится с ошибкой. Без строк 11–13 приложение попытается выполнить недопустимую инструкцию.
Печать на экран выводится при помощи системной функции «Системный вызов» операционной системы. В приложении мы вызываем функцию write(). Ее мы указываем, когда загружаем значение 4 в регистр процессора с названием r7 (строка 8). Далее выполняется инструкция swi $=0 (строка 9), где идет переход прямо в Linux-ядро, который является основой Android.
Что касается параметров для системного вызова, то они передаются через другие регистры. Например, r0 показывает номер дескриптора файла, который нам необходимо напечатать. Мы помещаем туда значение 1 (строка 5), указывающее стандартный вывод (stdout), то есть вывод на экран.
r1 указывает на адрес памяти данных, которые мы хотим записать, поэтому мы просто загружаем в эту область адрес строки «Hello, World» (строка 6), а регистр r2 показывает, сколько байтов мы хотим записать. В нашей программе для него установлено значение message_len (строка 7), вычисляемое в строке 18 с использованием специального синтаксиса: символ точки обозначает текущий адрес памяти. По этой причине. — message обозначает текущий адрес памяти минус адрес message. Ну а поскольку мы заявляем message_len сразу же после message, то все это вычисляется как длина message.
Если записать код строк 5–9 при помощи языка С, получится следующее:
#define message "Hello, World\n"
write(1, message, strlen(message));
Завершить работу программы несколько проще. Для этого мы просто прописываем код выхода в регистр r0 (строка 11), после чего добавляем значение 1, являющееся номером вызова системной функции exit(), в r7 (строка 12), затем снова вызываем ядро (строка 13).
Полный список системных вызовов Android и их номеров можно найти в исходном коде операционной системы. Также там есть и реализация write() и exit(), вызывающих соответствующие системные функции.
Собираем программу
Для того чтобы скомпилировать наш проект, понадобится Android NDK (Native Development Kit). Он содержит набор компиляторов и инструментов сборки для ARM-платформы. Загрузить его можно с официального сайта, установить — например, через Android Studio.
После того как NDK установлен, нам понадобится файл arm-linux-androideabi-as, это ассемблер для ARM. Если вы произвели загрузку через Android Studio, то поищите его в папке Android SDK. Обычно ее расположение —
ndk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin.
После того как ассемблер найден, сохраните написанное в файл с названием hello.s, после чего выполните следующую команду для преобразования его в машинный код:
arm-linux-androideabi-as -o hello.o hello.s
Эта операция позволяет создать объектный ELF-файл с именем hello.o. Для того чтобы преобразовать его в двоичный файл, который может работать на вашем девайсе, вызовите компоновщик:
arm-linux-androideabi-ld -o hello hello.o
Теперь у нас есть файл hello, который содержит программу, вполне готовую к использованию.
Запускаем приложение на своем девайсе
Приложения для Android обычно распространяются в .apk-формате. Это особый вид сжатого файла, который включает классы Java (да, можно писать и при помощи С / С++, но точкой входа должна быть Java).
Для того чтобы избежать проблем при запуске приложения, в примере был использован adb, что позволило скопировать его во временную папку нашего устройства Android. После этого пускаем в ход adb shell для того, чтобы запустить приложение и оценить результат:
adb push hello /data/local/tmp/hello
adb shell chmod +x /data/local/tmp/hello
И, наконец, запускаем приложение:
adb shell /data/local/tmp/hello
А что напишете вы?
Сейчас у вас есть рабочее окружение, похожее на то, которое было у Ариэллы. Она потратила на изучение ARM-ассемблера несколько дней, придумав затем несложный проект — это игра Sven Boom (разновидность Fizz Buzz родом из Израиля). Игроки считают по очереди и каждый раз, когда число делится на 7 или содержит число 7, они должны сказать «бум» (отсюда и название игры).
Стоит отметить, что программа по игре — не такая уж и простая задача. Ариэлла написала целый метод, который выводит на экран числа, по одной цифре за раз. Поскольку она писала все на ассемблере без вызова стандартных функций библиотеки С, на решение пришлось потратить несколько дней.
Первая программа Ариэллы для Adnroid размещена вот здесь. Кстати, некоторые идентификаторы кода в приложении — на самом деле еврейские слова (например, _sifra_ahrona).
Написание Android-приложения на ассемблере — хороший способ познакомиться поближе с архитектурой ARM, а также лучше понять внутреннюю кухню гаджета, используемого вами ежедневно. Я предлагаю вам заняться этим вплотную и попробовать создать небольшое приложение на ассемблере для вашего устройства. Это может быть простая игра или что-нибудь еще.
Skillbox рекомендует:
- Практический курс «Мобильный разработчик PRO».
- Онлайн-курс «С#-разработчик с нуля».
- Двухлетний практический курс «Я — веб-разработчик PRO».