image

Эта рассказ о нестандартном подходе к разработке 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 рекомендует:

Комментарии (13)


  1. Alexeyslav
    26.02.2019 15:25

    Писать на ассемблере можно но скучно. В итоге всё сводится к вызову функций, лучше бы применять ассемблер по прямому назначению — быстрая реализация алгоритмов которые сложнее написать на ЯВУ чем использовать интересные инструкции ассемблера «срезая углы».

    И непонятна ещё одна вещь… КУДА БУДЕТ ВЫВЕДЕН текст из примера? На андроиде программы вроде не консольные, а для вывода текста нужно как минимум место где «рисовать» а программа его не создаёт явно. Всё это скрыто внутри функций реализованных чисто в андроиде?

    Например, хорошо бы начать с таких наглядных алгоритмов как вычисление положения солнца и луны. Как бы такое счастье выглядело бы на ассемблере?


    1. Hivemaster
      26.02.2019 16:00

      КУДА БУДЕТ ВЫВЕДЕН текст из примера?

      В консоль adb.


    1. CrashLogger
      27.02.2019 13:27

      Писать обычные приложения на ассемблере скучно, но он для этого и не предназначен. А вот драйвера и прошивки для контроллеров — очень даже интересно. А если еще углубиться в реверс-инжиниринг и безопасность…


      1. Alexeyslav
        27.02.2019 18:21

        Вобщем-то не так и скучно, на макро-ассемблере. делаешь макро-кирпичики, и пишется не хуже С-шки. Но сложные программы писать будет уже трудно… т.к. как минимум никто вам «кирпичиков» готовых не напишет.


  1. Alexey2005
    26.02.2019 16:12

    ИМХО, изучать ARM удобнее всего на микроконтроллерах, например stm32. Там по крайней мере всё железо сразу доступно и нет ОС, которая только мешает. Всё программирование под Android — это по сути непрерывная война с ОС, её кривой архитектурой и несовместимостями.
    А ещё есть одноплатники вроде RPi, где тоже ARM, но при этом полноценный Linux со всем её инструментарием.


  1. ValdikSS
    26.02.2019 16:27

    Пишем Linux-приложение на ассемблере.


  1. saag
    26.02.2019 16:54

    Давайте сразу порт MenuetOS(KolibriOS) на телефон, потом SDK для разработки приложений под нее, ось будет всего 1,44 Мбайт размером, будет просто летать…


  1. DareDen
    26.02.2019 17:02

    1. читаем заголовок, думаем — «О! NativeActivity или что-то еще интересное!»
    2. читаем статью, обычный syscall, как в сотнях «Пишем hello world для Linux на ассемблере»
    ???
    3. огорчаемся, что профита таки нет и не будет…


    1. GarryC
      26.02.2019 17:18

      Самое главное, что непонятно, зачем на ассемблере, ведь он не для вывода строчек на консоль придуман.


  1. pfemidi
    26.02.2019 20:25

    Ничего не хочу сказать, но я это уже видел 22 февраля тут, правда другими словами и от другого переводчика.


  1. CrashLogger
    27.02.2019 13:24

    Я предлагаю вам заняться этим вплотную и попробовать создать небольшое приложение на ассемблере для вашего устройства. Это может быть простая игра или что-нибудь еще.


    Я предлагаю вам написать пример хотя бы создания Activity на ассемблере, тогда можно будет говорить об играх и прочем.


  1. FForth
    27.02.2019 16:37

    Писать на ассемблере для старших ARM, конечно, некоторый перебор, но вот например на Форт (GForth для Andrioid) автономно вполне возможно.
    Вот в нём можно и на встроенном ассемблере что то добавить к Форт-системе и посмотреть дизасемблированный код примитивных слов написанных или транслированных в ассемблер при сборке системы.
    www.blogbyben.com/2014/12/more-hand-held-hacking-forth-on-android.html

    P.S. Да, в Форт-системах для младших ARM как в коммерческих (Swift Forth, VFX Forth ...) так и свободных системах (например Mecrisp-Stellaris ...) ассемблер вполне штатное средство для использования.


  1. FForth
    28.02.2019 10:05

    Вот ещё некоторый материал для «затравки» по теме статьи. :)
    A Forth interpreter in ARM assembly github.com/gerard/uforth

    P.S. Уверен, что подобные примеры применения ассемблера для создания инструмента программирования можно найти ещё.

    В применимости ассемблера (без относительно ARM) для серъёзных проектов можно убедится на примере создания ядра рабочей ОС на ассемблере для ПК — KolibriOS.