Создание программ для MacOS ARM64

Первая программа для MacOS ARM64

Последнее обновление: 01.08.2023

В ноябре 2020 года компания Apple представила серию персональных компьютеров (Mac Mini) и ноутбуков (MacBook Air, MacBook Pro), которые работали на архитектуре Apple Silicon. Основу этой архитектуры составлял чип M1, который применял архитектуру ARM. С этого момента ноутбуки и персональные компьютеры на ARM стали активно распространяться на рынке. По некоторым оценкам доля ПК/ноутбуков на ARM на момент 2023 года состявляет около 15% и, как предполагается, в ближайшие годы будет расти. Из этого количества около 90% занимают компьютеры от фирмы Apple, то есть машины на базе чипов M1 и M2. Что делает актуальным разработку под эти устройства, в том числе с применением ассемблера. Рассмотрим, как создать первую программу на ассемблере ARM64 для MacOS.

Для создания программы на ассемблере ARM64 для MacOS прежде всего необходимо установить и настроить компилятор clang, который будет использоваться для компиляции программы. Для установки clang необходимо установить Xcode Command Line Tools. Эти инструменты устанавливаются вместе с XCode, поэтому самый простой способ установки компилятора - это установка XCode.

После установки XCode следует проверить корректность установки компилятора Clang. Для этого откроем терминал и выполним команду clang -v. Терминал должен отобразить версию clang, наподобие:

eugene@MacBook-Pro-Eugene arm64 % clang -v
Apple clang version 14.0.3 (clang-1403.0.22.14.1)
Target: arm64-apple-darwin22.5.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
eugene@MacBook-Pro-Eugene arm64 % 

Если консоль не распознает команду clang, и возникает ошибка следующего типа

eugene@MacBook-Pro-Eugene % clang -v
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
eugene@MacBook-Pro-Eugene %  

то в этом случае следует явным образом установить Xcode Command Line Tools с помощью следующей команды

xcode-select --install

Написание кода первой программы

Итак, когда clang установлен, мы можем приступать к написанию первой программы на ассемблере ARM64. В целом язык ассемблера для Apple Silicon аналогичен стандартному языку ассемблера ARM, который применяется в компиляторах GNU GCC. В свой документации компаниия Apple указывает разве что две особенности:

  • Регистр X18 зарезервирован, и его не следует использовать

  • Регистр указателя регистра стека (FP, X29) должен хранить валидное значение.

Однако есть еще ряд особенностей, связанных прежде всего с загрузкой данных, которые далее будут рассмотрены.

Для хранения файлов с исходным кодом определим какую-нибудь папку, например, назовем ее arm64, и в ней создадим файл, который будет называться hello.s и в который поместим следующий код:

//
// METANIT.COM. Программа на ассемблере для Mac OS Silicon, которая
// выводит на консоль строку "Hello METANIT.COM!"
//

.global _start		// Устанавливаем точку входа в программу для компоновщика
.align 2			// Для MacOS требуется выравнивание в 4 байта

// _start - точка входа в программу
_start: 
    mov	X0, #1		// значение 1 представляет стандартный поток вывода (консоль)
	adr	X1, message 	// передаем адрес строки для вывода на консоль
	mov	X2, #19	    	// размер строки в байтах
	mov	X16, #4		// номер системного вызова Unix для записи в поток (на консоль)
	svc	#0x80		// вызываем системную функцию с номером 4

// выход из программы
	mov     X0, #0		// устанавливаем код возврата
	mov     X16, #1		// системный вызов 1 завершает программу
	svc     #0x80		// вызываем системную функцию с номером 1

message: .ascii  "Hello METANIT.COM!\n"

Рассмотрим поэтапно данный код. Прежде всего стоит отметить, что два слеша // указывают на комментарий. Комментарии служат лишь для разъяснения тех или иных участков программы и на ее компиляцию и выполнение никак не влияют.

Вначале идет директива .global:

.global _start

Данная директива делает видимой извне определенную метку программы. В данном случае метку _start, которая является точкой входа в программу. Благодаря этому компоновщик при компоновке программы в исполняемый файл сможет увидеть данную метку.

Далее для работы программы на MacOS нам надо установить выравнивание по 4 байтам с помощью директивы .align:

.align 2

Директиве .align передается степень двойки для установки выравнивания последующиз инструкций. То есть здесь идет выравнивание по 22 = 4 байтам.

Далее идет метка _start, на которую собственно и проецируется программа. То есть эта метка - точка входа в программу. Название метки - произвольное, но обычно это или _start или _main

Что делает наша программа? Она будет выводить строку на консоль. Прежде всего в конце программы размещаем саму строку, которая проецируется на метку message:

message: .ascii  "Hello METANIT.COM!\n"

Для определения строки применяется директива .ascii, после которой в двойных кавычках располагается сама строка. И теперь нам надо вывести эту строку на консоль. Для взаимодействия с ресурсами операционной системы применяются системные вызовы (syscalls). В частности, для вывода на консоль нам нужно обратиться к системному вызову с номером 4 (каждый системный вызов имеет определенный номер) и передать ему некоторое значения.

Сначала с помощью инструкции MOV помещаем в регистр Х0 число 1:

mov	X0, #1

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

Затем с помощью инструкции ADR в регистр Х1 помещаем адрес выводимой строки - строки message:

adr	X1, message

Далее указываем размер строки в байтах и помещаем этот размер инструкцией MOV в регистр Х2 (в строке ASCII каждый символ занимает 1 байт):

mov	X2, #19

Далее в регистр Х16 помещаем номер системного вызова, который собственно и будет выводить строку на консоль:

mov	X16, #4

Это системный вызов с номером 4 или системная функция write.

Итак, мы все параметры установили, теперь вызовем сам системный вызов - для этого применяется инструкция svc, которой в качестве операнда передается шестнадцатеричное число 0x80

svc	#0x80

После выполнения данной инструкции на консоль будет выведена нужная нам строка. После этого нам надо завершить программу. Для завершения программы применяется системный вызов 1. Он принимает один параметр - код возврата, который помещается в регистр Х0:

mov	X0, #0

Обычно числовой код 0 говорит об успешном выполнении программы. В реальности же здесь может быть произвольное число. Далее в регистр Х16 помещаем номер системного вызова - 1 (системная функция exit):

mov	X16, #1

И в конце также с помощью инструкции svc обращаемся к этому системному вызову:

svc	#0x80

Компиляция программы

Для компиляции программы перейдем в терминале к папке, где располагается файл hello.s, и выполним следующую команду

as hello.s  -o hello.o -arch arm64

Данная команда обращается к ассемблеру as, получает файл с исходным кодом "hello.s" и компилирует его в объектный файл hello.o. Так как clang - многоплатформенный компилятор, то также с помощью флага -arch указываем, что мы компилируем под платформу arm64 (то есть под MacOS Silicon).

После этого в папке с исходным файлом появится объектный файл hello.o. Теперь его надо скомпилировать в исполняемый файл, чтобы мы могли его запускать на том же MacOS ARM64. Для этого в терминале выполним следующую команду

ld -o hello hello.o -l System -syslibroot `xcrun -sdk macosx --show-sdk-path` -e _start -arch arm64

Здесь программе компоновщика (линкера) передается набор опций. Вкратце пробежимся по ним:

  • -o hello hello.o

    Опция -o указывает, что надо скомпоновать объектный файл hello.o в исполняемый файл hello

  • -l System

    Эта опция указывает компоновщику связать наш исполняемый файл с библиотекой libSystem.dylib. Это делается, чтобы добавить команду загрузки LC_MAIN в исполняемый файл.

  • -syslibroot `xcrun -sdk macosx --show-sdk-path`

    Чтобы компоновщик смог найти библиотеку libSystem.dylib, необходимо сообщить ему, где эту библиотеку найти. Выражение xcrun -sdk macosx --show-sdk-path устанавливает путь к используемому для компоновки SDKб а точнее текущую активную версию Xcode.

  • -e _start

    По умолчанию в качестве точки входа в программу компоновщик рассматривает метку _main. В нашей же программу такой точкой входа, с которой начинается программа, является метка _start. И с помощью этого параметра сообщаем компоновщику, что точка входа - _start

  • -arch arm64

    Указывает, что мы компилируем под архитектуру arm64. Особенно это актуально, если мы компилируем для машины MasOS Silicon (на чипах M1/M2/M3) на компьютерах MacOS на процессоре Intel x86-64. Если же мы компилируем на компьтере MasOS Silicon, то этот параметр можно опустить.

После выполнения этой команды в текущей папке появится также исполняемый файл hello. И мы можем запустить его на выполнение командой

./hello

и на консоль должна быть выведена строка.

Полный вывод:

eugene@MacBook-Pro-Eugene arm64 % as -arch arm64 -o hello.o hello.s
eugene@MacBook-Pro-Eugene arm64 % ld -o hello hello.o -lSystem -syslibroot `xcrun -sdk macosx --show-sdk-path` -e _start -arch arm64
eugene@MacBook-Pro-Eugene arm64 % ./hello                          
Hello METANIT.COM!
eugene@MacBook-Pro-Eugene arm64 % 

Выше указанные команды компиляции и компоновки будут одинаковым образом работать и на MacOS Silicon, и на MacOS Darwin (Intel x86-64), единственное, что на MacOS Darwin мы не сможем запустить приложение, скомпилированное и скомпонованное для ARM64.

Однако если текущий компьютер, на котором выполняется компиляция приложения, работает на платформе Apple Silicon, то есть на той же платформе, для которой компилируется приложение, то из команд компиляции/компоновки мы можем убрать опцию -arch arm64:

as -o hello.o hello.s
ld -o hello hello.o -lSystem -syslibroot `xcrun -sdk macosx --show-sdk-path` -e _start
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850