Заметка основана на конспектах из книги Programming with 64-bit ARM Assembly Language.
Регистры Link to heading
X0-X30
– Регистры общего назначения.X31
,SP
,XZR
– Указатель стека или нулевой регистр, в зависимости от контекста.X30
,LR
– Ссылочный регистр. При вызове функции этот регистр используется для хранения адреса возврата. Не рекомендуется его использовать для чего-либо другого.PC
– Счётчик инструкций. Хранит адрес по которому в памяти расположена выполняемая в данный момент инструкция.
Мы не всегда хотим использовать все 64 бита данных регистра,
иногда нам достаточно 32 бит. Поэтому существуют удобные
32-битные регистры W0-W30
и WZR
. Когда мы их используем, то
верхние 32 бита соответствующего 64-битного регистра
устанавливаются в 0.
Формат инструкций Link to heading
Все инструкции имеют длину 32 бита. Остальное нам пока не очень важно для практических целей (если только мы не собираемся писать дизассемблер).
Тривиальная программа Link to heading
Рассмотрим “привет мир”.
X0-X2
– Параметры системных вызовов.X16
– Номер системного вызова.
; сообщаем линковщику где начинается программа
.global _start
; устанавливаем нужное выравнивание
.align 2
_start:
// печатаем
MOV X0, #1 ; куда печатать: 1 = stdout
ADR X1, helloworld ; что печатать
MOV X2, #13 ; длина нашей строчки
MOV X16, #4 ; номер системного вызова "write"
SVC #0x80 ; делаем системный вызов
// выходим
MOV X0, #0 ; код 0
MOV X16, #1 ; номер системного вызова "exit"
SVC #0x80 ; просим ядро завершить программу
.data
helloworld: .ascii "Hello World!\n"
Вот так можно собрать:
as -arch arm64 -o HelloWorld.o HelloWorld.s
ld -o HelloWorld HelloWorld.o -lSystem -syslibroot `xcrun -sdk macosx --show-sdk-path` -e _start -arch arm64
Базовые инструкции Link to heading
MOV/MOVK/MOVN Link to heading
Существует несколько форм инструкций перемещения данных:
MOVK XD, #imm16{, LSL #shift}
MOV XD, #imm16{, LSL #shift}
MOV XD, XS
MOV XD, operand2
MOVN XD, operand2
Здесь:
XD
иXS
– Какие-то любые регистры.#imm16
(immediate value) – Любой 16-битный числовой литерал.operand2
– Про него ниже.
Рассмотрим их по очереди.
MOVK (move keep) Link to heading
Загружает 16-битное число в одну из 4-х позиций регистра, не
трогая остальные 48 бит. Например, если мы хотим загрузить число
0x1234FEDC4F5D6E3A
в регистр X2
, то мы можем это сделать
так:
MOV X2, #0x6E3A
MOVK X2, #0x4F5D, LSL #16
MOVK X2, #0xFEDC, LSL #32
MOVK X2, #0x1234, LSL #48
В этом примере выше первая инструкция MOV
это алиас MOVZ
.
Инструкция MOVZ
ведёт себя идентично MOVK
за исключением
того, что она обнуляет остальные 48 бит.
MOV (из регистра в регистр) Link to heading
Копирует содержимое регистра X2
в регистр X1
:
MOV X1, X2
Что такое operand2 Link to heading
Все ARM-инструкции, которые работают с данными, имеют
опциональный параметр operand2
. Он может быть в 3 формах:
- Регистр и сдвиг
MOV X1, X2, LSL #1 ; логический сдвиг влево MOV X1, X2, LSR #1 ; логический сдвиг вправо MOV X1, X2, ASR #1 ; арифметический сдвиг вправо MOV X1, X2, ROR #1 ; сдвиг вправо (вращение)
- Регистр и расширение. Операции расширения позволяют нам
достать байт, половину или целое слово из второго регистра.
С инструкцией
MOV
расширения использовать нельзя, поэтому приведём пример сADD
:Если можно было догадаться что делают сдвиги, то с; X2 = X1 + SXTB(X0) ADD X2, X1, X0, SXTB
SXTB
и другими расширениями уже не так очевидно. Сейчас не будем на этом детально останавливаться. Надеюсь, что сможем изучить как работают расширения позже. - Число и сдвиг. Мы уже встречали эту форму, когда
разбирались с
MOVK
.MOV X1, 0xAB00, LSL #16
Попробуем MOV
на практике:
.global _start
.align 2
_start:
; помещаем число 0x1234FEDC4F5D6E3A в регистр X2
MOV X2, #0x6E3A
MOVK X2, #0x4F5D, LSL #16
MOVK X2, #0xFEDC, LSL #32
MOVK X2, #0x1234, LSL #48
; копируем содержимое W2 в W1
MOV W1, W2
; пробуем мнемоники сдвигов и вращений
LSL X1, X2, #1 ; логический сдвиг влево
LSR X1, X2, #1 ; логический сдвиг вправо
ASR X1, X2, #1 ; арифметический сдвиг вправо
ROR X1, X2, #1 ; сдвиг вправо (вращение)
; выходим
MOV X0, #0 ; код 0
MOV X16, #1 ; номер системного вызова "exit"
SVC #0x80 ; просим ядро завершить программу
Пора начать использовать мейк-файлы:
OBJS = mov.o
ifdef DEBUG
DEBUGFLGS = -g
else
DEBUGFLGS =
endif
LDFLAGS = -lSystem -syslibroot `xcrun -sdk macosx --show-sdk-path` -e _start -arch arm64
%.o : %.s
as $(DEBUGFLGS) $< -o $@
all: mov
mov: $(OBJS)
ld -o mov $(LDFLAGS) $(OBJS)
clean:
rm *.o mov
А теперь посмотрим что получилось objdump
‘ом:
objdump -s -d -M no-aliases mov.o
Покажет нам следующее:
mov.o: file format mach-o arm64
Contents of section __TEXT,__text:
0000 42c78dd2 a2eba9f2 82dbdff2 8246e2f2 B............F..
0010 e103022a 41f87fd3 41fc41d3 41fc4193 ...*A...A.A.A.A.
0020 4104c293 000080d2 300080d2 011000d4 A.......0.......
Disassembly of section __TEXT,__text:
0000000000000000 <ltmp0>:
0: 42 c7 8d d2 mov x2, #28218
4: a2 eb a9 f2 movk x2, #20317, lsl #16
8: 82 db df f2 movk x2, #65244, lsl #32
c: 82 46 e2 f2 movk x2, #4660, lsl #48
10: e1 03 02 2a orr w1, wzr, w2
14: 41 f8 7f d3 lsl x1, x2, #1
18: 41 fc 41 d3 lsr x1, x2, #1
1c: 41 fc 41 93 asr x1, x2, #1
20: 41 04 c2 93 extr x1, x2, x2, #1
24: 00 00 80 d2 mov x0, #0
28: 30 00 80 d2 mov x16, #1
2c: 01 10 00 d4 svc #0x80
Обратим внимание, например, что MOV X0, #0
была транслирована в ORR W1, WZR, W2
.
ADD/ADC Link to heading
Эти инструкции складывают второй и третий параметры и сохраняют
результат сложения в первый: Xd = Xs + Operand2
, где Xd
и
Xs
могут совпадать.
ADD{S} Xd, Xs, Operand2
Рассмотрим несколько примеров.
-
Сложение с константой. Число может быть до 12 бит (0-4095).
; X2 = X1 + 4000 ADD X2, X1, #4000
-
Со сдвигом влево.
; X2 = X1 + 0x20000 ADD X2, X1, #0x20, LSL 12
-
Простое сложение 2-х регистров.
; X2 = X1 + X0 ADD X2, X1, X0
-
Сложение регистра со сдвинутым значением в другом регистре.
; X2 = X1 + (X0 * 4) ADD X2, X1, X0, LSL 2
SUB/SBC Link to heading
SUB{S} Xd, Xs, Operand2
Память Link to heading
Для чтения и записи памяти используются инструкции STR
и LDR
соответственно.
LDR X1, =num ; загружает адрес num в регистр X1
LDR X3, =loc ; загружает адрес loc в регистр X3
LDR X2, [X3] ; загружает слово по адресу X3 в X2
.data
num .BYTE 0x12
loc .QUAD 0x123456789ABCDEF0
Функции и стек Link to heading
Стек растёт вниз, SP
указывает на его вершину и всегда
выровнен на 16 байт (это важно). У нас есть инструкции STR
и
STP
для размещения содержимого регистров в стеке, а также
LDR
и LDP
для считывания данных из стека в регистры.
Чтобы скопировать в стек значение из регистра X5
, в котором
хранится число 1022
:
STR X5, [SP, #-16]!
Чтобы загрузить в регистр X4
значение из вершины стека, где
лежит число 1022
:
LDR X4, [SP], #16