Рассмотрим компиляцию программы на MacOS для Linux ARM64/Android. (Создание программ под MacOS ARM64 или Apple Silicon рассматривается далее).
Наиболее популярным инструментом для компиляции кода ассемблера для arm представляет компилятор GAS от проекта GNU, который входит в состав комплекта
инструментов для разработки под ARM - Arm GNU Toolchain. Итак, вначале установим данный набор инструментов. Для этого перейдем на официальный сайт компании Arm
на страницу https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads.
Здесь представлены поседние версии Arm GNU Toolchain
для разных архитектур.
(Если в силу географической принадлежности доступ к сайту блокируется, то необходимый пакет инструментов для MacOS Intel x86-64 можно загрузить отсюда - arm-gnu-toolchain-13.2.rel1-darwin-x86_64-aarch64-none-elf.pkg)
Стоит отметить, для MacOS инструменты Arm GNU Toolchain
доступны в двух вариантах - для архитектуры macOS x86_64 (архитектура Darwin) и для архитектуры Apple silicon (ARM M1/M2). Причем для каждой архитектуры доступны две группы пакетов:
AArch32 bare-metal target (arm-none-eabi)
: для компиляции программ под 32-битные архитектуры без привязки к конкретной операционной системе
AArch64 bare-metal target (aarch64-none-elf)
: для компиляции программ под 64-битные архитектуры без привязки к конкретной операционной системе
Поскольку нас интересует архитектура arm64, то обратимся к пакету AArch64 bare-metal target (aarch64-none-elf)
. Он доступен
в двух вариантах: установочный файл pkg, который устанавливает все необходимые файлы, и
tar.xz - архив, который содержит по сути те же самые файлы, которые мы можем распаковать в любое нужное для нас расположение. Выберем
pkg-пакет для нашей архитекрутры. Например, если у нас MacOS на платформе Intel x86-64, то для загрузки выберем файл, который имеет в своем названии "darwin-x86_64" (в моем случае это
файл arm-gnu-toolchain-12.2.rel1-darwin-x86_64-aarch64-none-elf.pkg
)
После загрузки загрузочный пакет и выполним пошаговую установку
По умолчанию этот комплект устанавливается в системе в папку программ. В моем случае это /Applications/ArmGNUToolchain/12.2.rel1/aarch64-none-elf/bin
.
В этом комплекте нам понадобится прежде всего сам ассемблер - файл aarch64-none-elf-as, который по коду ассемблера Arm64 создает объектный файл. Кроме того, также потребуется файл aarch64-none-elf-ld, который также располагается в этой папке и который генерирует из объектного файла исполныемый файл.
Чтобы не прописывать каждый полный путь к компилятору, добавим в переменные среды путь к компилятору (к папке bin). Для этот введем в терминале команду
nano ~/.zshrc
Она создаст (если его еще нет) и откроет файл .zshrc
. В этом файле пропишем в файле строку
export PATH=/Applications/ArmGNUToolchain/12.2.rel1/aarch64-none-elf/bin:$PATH
Перезагрузим терминал, чтобы изменения вступили в силу. Затем проверим установку компилятора следующей командой:
aarch64-none-elf-as --version
Мы должны получить вывод типа следующего:
eugene@MacBook-Pro-Eugene ~ % aarch64-none-elf-as --version GNU assembler (Arm GNU Toolchain 12.2.Rel1 (Build arm-12.24)) 2.39.0.20221210 Copyright (C) 2022 Free Software Foundation, Inc. This program is free software; you may redistribute it under the terms of the GNU General Public License version 3 or later. This program has absolutely no warranty. This assembler was configured for a target of `aarch64-none-elf'. eugene@MacBook-Pro-Eugene ~ %
Теперь напишем первую простейшую программу, которая просто будет выводить на консоль некоторую строку. Для этого создадим какой-нибудь каталог, например, arm
.
Создадим в этого каталоге новый файл hello.s (обычно файлы с кодом ассемблера arm имеют расширение .s). Определим в этом файл следующий код:
.global _start // устанавливаем стартовый адрес программы _start: mov X0, #1 // 1 = StdOut - поток вывода ldr X1, =hello // строка для вывода на экран mov X2, #19 // длина строки mov X8, #64 // устанавливаем функцию Linux svc 0 // вызываем функцию Linux для вывода строки mov X0, #0 // Устанавливаем 0 как код возврата mov X8, #93 // код 93 представляет завершение программы svc 0 // вызываем функцию Linux для выхода из программы .data hello: .ascii "Hello METANIT.COM!\n" // данные для вывода
Для большего понимания я снабдил программу комментариями. GNU Assembler использует тот же самый синтаксис комментариев, что и C/C++ и другие си-подобные
языки: одиночный комментарий начинается с двойного слеша //. Также можно использовать многострочный комментарий с помощью символов /∗ и ∗/, между которыми помещается
текст комментария (/* текст комментария */
Вначале надо указать линкеру (в нашем случае программа ld) стартовую точку программы. В данной программе стартовая точка программы проецируется
на метку _start
. И чтобы линкер получил к ней доступ, определяет _start
в качестве глобальной переменной с помощью оператора global.
.global _start
Одна программа может состоять из множества файлов, но только один из них может иметь точку входа в программу _start
Далее идут собственно действия программы. Вначале вызывается инструкция mov, которая помещает данные в регистр.
mov X0, #1
Значения X0-X2
представляют регистры для параметров функции в Linux. В данном случае помещаем в регистр X0 значение "#1". Операнды начинаются со знака "#"
Число 1 представляет номер стандартного потока вывода "StdOut".
Далее загружаем в регистр X1 адрес строки для вывода на экран с помощью инструкции ldr
ldr X1, =hello
Затем также с помощью инструкции mov помещаем в регистр X2 длину выводимой строки
mov X2, #19
Для любого системного вызова в Linux параметры помещаются в регистры X0–X7 в зависимости от количества. Затем в регистр X0 помещается код возврата. А сам системный вызов
определяется номером функции из регистра X8. Здесь помещаем в X8 функцию с номеро 64 (функция write
)
mov X8, #64
Далее выполняем системный вызов с помощью оператора svc
svc 0
Операционная система, используя параметры в регистрах и номер функции, выведет строку на экран.
После этого нам надо выйти из программы. Для этого помещаем в регистр X0 число 0
mov X0, #0
А в регистр X8 передаем число 93 - номер функции для выхода из программы (функция exit
)
mov X8, #93
И с помощью svc также выполняем функции. После этого программа должна завершить выполнение.
В самом конце программы размещена секция данных
.data hello: .ascii "Hello METANIT.COM!\n" // данные для вывода
Оператор .data указывает, что дальше идет секция данных. Выражение .ascii выделяет память и помещает в нее указанную далее строку.
Строка завершается символом перевода строки "\n", чтобы не надо было нажимать на Return, чтобы увидеть текст в окне терминала.
Для компиляции приложения откроем терминал/командную строку и командой cd перейдем к папке, где расположен файл hello.s с исходным кодом программы. И для компиляции выполним команду:
aarch64-none-elf-as hello.s -o hello.o
Компилятору aarch64-none-elf-as
в качестве параметра передается файл с исходным кодом hello.s. А параметр -o
указывает, в какой файл будет компилироваться
программа - в данном случае в файл hello.o. Соответственно в папке программы появится файл hello.o
Затем нам нужно скомпновать программу с исполняемый файл с помощью линкера aarch64-none-elf-ld
командой:
aarch64-none-elf-ld hello.o -o hello
После этого в папке программы появится исполняемый файл hello, который мы можем запускать на устройстве с архитектурой ARM под управлением Linux.
Итак, у нас есть исполняемый файл программы. Мы ее можем протестировать. Для этого нам нужен Linux на устройстве с архитектурой ARM. В качестве такого устройства я возьму самый распространенный вариант - смартфон под управлением Android. Поскольку Android построен на базе Linux и как правило устанавливается на устройства с arm архитектурой.
Для установки файла на Android нам понадобится консольная утилита adb, которая устанавливается в рамках Android SDK. Android SDK обычно устанавливается вместе с Android Studio. Но если Android Studio не установлена, или необходимо установить отдельно, то можно загрузить пакет со страницы https://developer.android.com/studio/releases/platform-tools. Например, ссылка на пакет для архитектуры x86-64(Darvin): https://dl.google.com/android/repository/platform-tools-latest-darwin.zip. В составе этого пакета или в составе SDK в папке platforms-tools можно найти нужную нам утилиту adb.
Чтобы не набирать полный путь к adb
, лучше прописать путь к ней в переменных среды.
Теперь переместим скомпилированный файл hello на устройство под Android. Для этого перейдем в консоли с помощью команды cd к папке с файлом hello используем следующую команду
[Путь_к_файлу_adb]/adb push hello /data/local/tmp/hello
То есть в данном случае используем команду push для помещения копии файла hello на смартфон в папку /data/local/tmp/
Далее перейдем к консоли устройства Android с помощью команды:
adb shell
Далее перейдем к папке /data/local/tmp с помощью команды
cd /data/local/tmp
Затем изменим режим файла, чтобы его можно было запустить:
chmod +x hello
и в конце выполним файл hello
./hello
И на консоль должна быть выведена строка "Hello METANIT.COM!"