Читать книгу Ассемблер ARM64 (Андрей Викторович Ипполитов) онлайн бесплатно на Bookz
bannerbanner
Ассемблер ARM64
Ассемблер ARM64
Оценить:

5

Полная версия:

Ассемблер ARM64

Андрей Ипполитов

Ассемблер ARM64


Об авторе


Андрей – родился в Москве и с раннего возраста увлекся программированием. После получения образования в «МГУПС», он много экспериментировал с разными языками программирования. Автор живет в Подмосковье, помимо писательства, активно занимается созданием программы kitasm. Его жизненная философия вдохновляет читателей на программирование.

Сейчас Андрей работает над новыми проектами и продолжает радовать своих поклонников свежими сюжетами и идеями.


Пролог


Ночь в лаборатории была тёплой и тихой – свет индикаторов на панелях мерцал, как звёзды в искусственном небе. За столом, у старого терминала с потёртым шрифтом на экране, сидел человек, для которого цифры и биты были больше, чем просто знаки: они были языком мышления, тайной, которую стоило расшифровать.

Он вспомнил свои первые шаги: как нерешительно вводил команды, наблюдая за реакцией машины, как одна простая инструкция могла изменить поток исполнения, как каждая метка и каждое смещение были крошечными рычагами управления вселенной микропроцессора. Ассемблер казался тогда заклинанием – строгой рифмовкой операторов и регистров, где ошибка в одном символе могла превратить систему в молчаливого монстра.

Эта книга – не о магии и не о забытом ремесле, а о языке, который позволяет взглянуть в сердце вычислений. Здесь нет волшебных трюков, только чистая логика и точность: как строятся инструкции, как передаются данные, как осуществляется контроль потока, как из простых операций вырастает сложная программа. Ассемблер – это алхимия простых операций, превращающая электрические импульсы в смысл.

Читатель встретит здесь не сухие описания и не абстрактные теории, а практику: примеры, отладочные приёмы, шаблоны решения типовых задач и объяснения, почему тот или иной подход оказывается эффективнее. Мы пройдём путь от базовых команд к реальным программам, от простейших циклов до управления прерываниями, работы с памятью и оптимизации исполнения.

Если вы пришли из высокоуровневых языков – приготовьтесь к смене парадигмы. Ассемблер потребует точности мышления, понимания архитектуры и умения мыслить в терминах машинных состояний. Но взамен он даст необычную, почти интимную близость к машине: вы научитесь слышать её ритм и предугадывать её поведение.

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

Перед началом: выключите лишние шумы, подготовьте отлаживающую среду и запаситесь терпением. Путь будет нелёгким, но каждое пройденное испытание откроет новую грань понимания того, как устроен мир компьютеров – от стека вызовов до мельчайших переключений флагов.


Добро пожаловать в мир, где байты говорят правду.


Введение


Добро пожаловать в мир низкоуровневого программирования, где каждая инструкция имеет значение, а аппаратное обеспечение раскрывает свои самые сокровенные тайны! Эта книга – ваш проводник в увлекательное путешествие по архитектуре ARM64 и языку ассемблера, ориентированному на платформу macOS. Если вы когда-либо задумывались, как на самом деле работают программы, что скрывается за магией высокоуровневых языков, или стремитесь получить максимальную производительность от своего Mac, то эта книга для вас.


Почему ARM64 и MacOS?

Современные устройства Apple, от iPhone и iPad до MacBook и Mac Pro, работают под управлением процессоров на базе архитектуры ARM64. Это означает, что понимание ARM64 – это ключ к пониманию сердца большинства устройств, которыми мы пользуемся каждый день. macOS, как операционная система, тесно интегрирована с этой архитектурой, предоставляя уникальные возможности и инструменты для разработки на ассемблере.


Что такое Assembler?

Assembler (или язык ассемблера) – язык программирования, который находится на очень низком уровне. Он представляет собой почти прямое отображение машинных инструкций, которые процессор выполняет напрямую. В отличие от высокоуровневых языков, таких как Python, Java или C++, где вы работаете с абстракциями и командами, ассемблер требует от вас понимания таких вещей, как:

Регистры: Крошечные, сверхбыстрые ячейки памяти внутри процессора, используемые для временного хранения данных и адресов.

Инструкции: Фундаментальные операции, которые процессор может выполнять, такие как сложение, вычитание, перемещение данных, условные переходы и вызовы функций.

Память: Адресация ячеек основной памяти, где хранятся данные и код программы.

Архитектура набора команд: Набор всех инструкций, которые может выполнять конкретный процессор.


Почему стоит изучать Assembler на ARM64 MacOS?

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

Глубокому пониманию работы компьютера: Вы начнете видеть, как программы взаимодействуют с аппаратным обеспечением на самом фундаментальном уровне.

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

Разработке операционных систем и драйверов: Эти области часто требуют прямого взаимодействия с аппаратным обеспечением.

Реверс-инжинирингу и анализу вредоносного ПО: Понимание ассемблера – неотъемлемая часть изучения того, как программы работают “изнутри”, что важно для безопасности.

Созданию кросс-платформенных решений: Знание

ARM64

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

Отладке сложных проблем: Иногда только понимание ассемблерного кода может помочь выявить и исправить трудноуловимые ошибки.


Что вы узнаете из этой книги:

В этой книге мы последовательно разберем:

Основы архитектуры

ARM64:

Понимание регистровой модели, режимов выполнения и общих концепций.

Базовые инструкции

ARM64:

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

Системные вызовы

macOS:

Как ваша программа взаимодействует с операционной системой для выполнения таких задач, как ввод/вывод, выделение памяти и работа с файлами.

Процесс сборки и отладки: Использование инструментов, доступных на

macOS

, для компиляции и отладки ассемблерного кода. Практические примеры: От простых программ до более сложных задач, демонстрирующих применение полученных знаний.

Особенности

ARM64

на

macOS:

Специфические моменты, связанные с работой на этой платформе.


Кому предназначена эта книга:

Начинающим программистам: Если вы хотите копнуть глубже, чем просто написание скриптов.

Опытным разработчикам: Желающим расширить свои знания и освоить новые парадигмы программирования.

Системным администраторам и

DevOps-

специалистам: Интересующимся внутренним устройством систем.

Студентам технических специальностей: Изучающим информатику, компьютерную инженерию или смежные области.


Предварительные знания:

Для успешного освоения материала желательно иметь базовое представление о принципах работы компьютеров и программирования. Знакомство с каким-либо высокоуровневым языком программирования также будет плюсом, так как вы сможете проводить параллели и лучше понимать абстракции.


Путь к мастерству:

Изучение ассемблера – это марафон, а не спринт. Не ожидайте, что вы станете экспертом за одну ночь. Главное – это терпение, практика и последовательность. Я предлагаю вам вместе шаг за шагом разбирать каждую тему, решать практические задачи и экспериментировать.

Готовы ли вы к этому захватывающему путешествию? Откройте для себя мир, где код оживает, и где вы контролируете каждое действие процессора.


Далее, по умолчанию, все относится к ассемблеру GAS для MacOS на чипе ARM64 (M chip)


Прежде чем начать


Прежде чем писать код, нам нужно убедиться, что у нас есть все необходимое. На macOS для разработки на ассемблере нам потребуются:

Xcode Command Line Tools: Это набор утилит командной строки, который включает в себя компилятор (ассемблер) as и компоновщик ld, а также отладчик lldb. Если они еще не установлены, вы можете сделать это, выполнив в Терминале:


$ xcode-select —install


Затем установим IDE. IDE расшифровывается как интегрированная среда разработки (Integrated Development Environment). Это программное обеспечение, которое предоставляет разработчикам следующие функции:

Редактор кода: удобный инструмент для написания и редактирования программного кода.

Компилятор или интерпретатор: преобразует написанный код в исполняемый файл.

Отладчик: помогает находить и устранять ошибки в коде.

IDE значительно упрощает процесс разработки, собирая все необходимые инструменты в одном приложении. Мы скачаем и установим программу kitasm. Для установки пройдите на сайт www.kitasm.site и загрузите последнюю версию. Программа платная, но можно скачать демо версию, она тоже подойдет. Затем разархивируйте пакет с программой, войдите в терминал и введите команду:


$ sudo xattr -r -d com.apple.quarantine kitasm.app


Эта команда разрешит запуск программы, загруженной из интернета. Теперь запустим kitasm.

Программа kitasm

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


Глава 1 Первая программа


Создание первой программы с использованием ассемблера – увлекательный процесс, который позволяет увидеть, как работает низкоуровневое программирование. Давайте создадим простую программу, которая выводит текст на экран.


Hello world

Откроем kitasm и напишем следующий код:


.global _start

.text

_start:

; Системный вызов для записи (write)

; x0 = файловый дескриптор (1 – stdout)

; x1 = указатель на буфер с сообщением

; x2 = длина сообщения

mov x0, 1 ; stdout

adr x1, message ; Указатель на строку message

mov x2, #13 ; Длина строки "Hello, ARM64!\n"

mov x16, 4 ; Номер системного вызова SYS_write

svc #0 ; Вызов ядра

; Системный вызов для завершения программы (exit)

; x0 = код возврата (0 – успешно)

mov x0, 0 ; Код возврата 0

mov x16, 1 ; Номер системного вызова SYS_exit

svc #0 ; Вызов ядра

message:

.ascii "Hello, World!\n"


Теперь сохраним в «Hello, World.s» и запустим его на выполнение. Для этого нажимаем правую кнопку мыши и выбираем пункт «run». Если все прошло успешно

внизу программы kitasm, в окне input/output вы увидите вывод фразы «Hello world!». Также там будет показан выход из программы с параметром 0. Это означает, что программа завершилась без ошибок.


Hello, World!

End program. Exitcode: 0


В папке куда вы сохранили, появится исполняемый файл «helloworld». Его можно запустить, написав в терминале команду:


$ ./helloworld


Комментарии в ассемблере обозначаются символом «;» или

«//«


Поздравляю! Вы только что написали и запустили свою первую программу на ассемблере. Этот простой пример иллюстрирует базовые принципы взаимодействия с операционной системой и работу с системными вызовами на низком уровне. Дальнейшее изучение ассемблера открывает множество возможностей для оптимизации кода и понимания работы компьютеров на более глубоком уровне.


Разберем код построчно:


.global

start

: Эта директива сообщает компоновщику, что метка _

start

является глобальной. В

macOS

точка входа в программу (начало исполнения) обычно называется _

start

.


.text

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


_

start

:

Это метка. В ассемблере метки используются для обозначения адресов инструкций или данных. К ним можно обращаться для переходов или вызовов

.


mov x0, 1

: Инструкция

mov

(

move

) перемещает значение 1 в регистр

x

0. Регистры

x

0-

x

30 используются для передачи аргументов системным вызовам и функциям. Для системного вызова

write

,

x

0 содержит файловый дескриптор. 1 означает стандартный вывод (

stdout

), куда обычно выводятся данные на консоль.


adr x1, message:

Инструкция

adr

(

address

) загружает в регистр x1 адрес метки

message. x1

будет содержать указатель на строку, которую мы хотим вывести.


mov x2, #13

: Загружаем число 13 в регистр

x

2. Это длина нашей строки “

Hello,

World

!\n”. Символ новой строки

\n

также занимает один байт

.


mov x16, 4

: для выполнения системных вызовов используется регистр

x

16. Номер

4

соответствует системному вызову

SYS_write.


svc #0

: Инструкция

svc

(

supervisor

call

) инициирует системный вызов. Она передает управление ядру операционной системы, которое выполнит запрошенную операцию (в данном случае, запись данных в

stdout

).

#0

указывает

,

что это стандартный вызов

.


mov x0, 0

: Для системного вызова

exit (

завершение программы), регистр

x

0 содержит код возврата. 0 означает

,

что программа завершилась успешно

.


mov x16, 1

: Номер 1 соответствует системному вызову

SYS

_

exit

.


message:

: Метка для нашей строки.


.ascii "Hello,

World

!\n"

: Директива .

ascii

определяет строку, состоящую из

ASCII

–символов.

\n

– это символ новой строки

.


Глава 2 Синтаксис ассемблера


Эта глава даёт практический и структурированный обзор синтаксиса ARM64 (AArch64) для GAS. Пояснения охватывают секции, директивы, регистры, инструкции, адресацию, соглашение о вызовах и типичные идиомы. Основные понятия (регистры, память, инструкции)


Байт – это базовая единица измерения информации в компьютерах, состоящая из 8 бит. Каждый бит может принимать значение 0 или 1, поэтому байт может представлять 256 различных комбинаций (от 0 до 255 в десятичной системе) или в шестнадцатеричной системе (от 0 до FF)


D9


Hex D

Hex 9


1

1

0

1

1

0

0

1


Байт состоит из 8 битов (нули и единицы)


Числа

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


Представление чисел

В ассемблере числа могут быть представлены в следующих форматах:


Десятичные числа:

Представляются в десятичной системе счисления, например: 10.


Шестнадцатеричные числа: Представляются в шестнадцатеричной системе счисления, например: 0

x

10 или 10

h

.


Двоичные числа:

Представляются в двоичной системе счисления, хотя напрямую синтаксис для двоичных чисел не поддерживается.


Типы чисел

Целые числа:

Представляются как 8-битные, 16-битные, 32-битные или 64-битные значения.

Дробные числа:

Не поддерживаются напрямую в

ARM64

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


Представление чисел в памяти

Числа в памяти представляются в виде двоичных или шестнадцатеричных кодов. Например:

8-битное целое число:

`0x10

64-битное целое число:

`0x0000000000000010


Операции с числами

В ассемблере поддерживаются различные операции с числами, такие как:

Сложение:

ADD X0, X1, X2 ; X0 = X1 + X2

Вычитание:

SUB X0, X1, X2 ; X0 = X1 – X2

Умножение:

MUL X0, X1, X2 ; X0 = X1 * X2

Деление:

UDIV X0, X1, X2 ; X0 = X1 / X2 (

беззнаковое)


Пример использования чисел

section .data

num1: .quad 10

num2: .quad 20

section .text

global _start

_start:

; Загрузка чисел в регистры

LDR X0, =num1 ; X0 = 10

LDR X1, =num2 ; X1 = 20

; Сложение

ADD X2, X0, X1 ; X2 = 30

; Вычитание

SUB X3, X2, X0 ; X3 = 20

RET


Числовые константы могут быть использованы напрямую в коде или объявлены с помощью директив.


.equ MY_CONST, 100

MOV X0, #MY_CONST ; X0 = 100


Числа играют ключевую роль в представлении данных и выполнении арифметических операций. Понимание того, как правильно использовать и представлять числа в различных форматах, необходимо для написания эффективных и корректных программ на языке ассемблера.


Переменные

В программировании на языке ассемблера, переменные и метки играют ключевую роль в обозначении и использовании данных в программе. Понимание того, как правильно использовать переменные и метки, необходимо для написания эффективных и корректных программ.


Переменные в ассемблере представляют собой области памяти, зарезервированные для хранения данных. Объявление переменной в ассемблере обычно включает в себя резервирование памяти под нее и возможное присвоение ей начального значения.

Объявление переменных

Переменные в ассемблере объявляются с помощью директив, таких как .byte, .half, .word, .quad и др., которые определяют размер переменной в байтах.


.byte: определяет байт (8 бит)

.half:

определяет полуслово (16 бит)

.word:

определяет слово (32 бита)

.dword:

определяет двойное слово (64 бита)

.

quad

: определяет двойное слово (64 бита)

.asciz:

определяет строку ASCII, завершающуюся нулевым байтом

.ascii:

определяет строку ASCII, не завершающуюся нулевым байтом


section .data my_byte: .byte 10

my_word: .word 20

my_quad: .quad 30


Метки

Метки в ассемблере используются для обозначения определенных мест в программе или адресов памяти. Они могут быть использованы для перехода к определенному участку кода или для ссылки на переменные.

Метки обычно записываются с помощью идентификатора и двоеточия:


my_label: ; Код


Типы меток

Метки перехода: Используются для указания места в программе, к которому можно перейти с помощью инструкции перехода (например

, B, BL).

Метки адреса: Используются для обозначения адресов переменных или данных.


section .data

var1: .quad 10

str1: .asciz "Hello, World!"

section .text

global _start

_start:

; Загрузка значения переменной

LDR X0, =var1 ; X0 = адрес var1

; Загрузка значения из памяти

LDR X1, [X0] ; X1 = значение по адресу var1 = 10

; Использование метки для перехода

my_loop:

; Код цикла

B my_loop ; Переход на my_loop

; Использование метки для данных

LDR X2, =str1 ; X2 = адрес str1

RET


Директива .global позволяет сделать метку или переменную доступной из других объектных файлов при линковке.


.global my_variable my_variable: .quad 100


Переменные и метки являются фундаментальными понятиями в программировании на языке ассемблера. Правильное использование переменных для хранения и манипуляции данными, а также меток для обозначения адресов и переходов, необходимо для создания эффективных и корректных программ.


Константы

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


Типы констант

Числовые константы: Это наиболее простой тип констант, представляющий собой конкретное числовое значение. Числовые константы могут быть заданы в различных системах счисления, таких как десятичная, шестнадцатеричная, двоичная.

Адресные константы: Используются для представления адресов памяти. Они часто применяются для указания адресов функций, переменных или меток.

ASCII

–строки: Строки, представленные в виде последовательности

ASCII

–символов.


Представление констант


Числовые константы

:


Десятичные числа записываются как обычно, например: 10.

Шестнадцатеричные числа начинаются с префикса

0x,

например: 0

x

10.

Двоичные числа можно представить, используя соответствующую двоичную запись, хотя напрямую синтаксис для двоичных чисел не поддерживается, их можно выразить через эквивалентные десятичные или шестнадцатеричные значения.


В ассемблере константы можно использовать в различных контекстах, таких как:

Непосредственные значения: 10 использоваться в инструкциях, например,

MOV

X

0, #10 загружает значение 10 в регистр

X

0.

Адреса: Константы могут использоваться для задания адресов, к которым будут обращаться инструкции загрузки/сохранения, например

, LDR X0, =my_var

загружает адрес метки

my_var

в регистр

X

0.


Пример использования констант

; Пример использования констант section .data

my_str: .asciz "Hello, World!" ; ASCII-строка

num: .quad 10 ; 64-битная константа

section .text

global _start

_start:

; Использование непосредственного значения

MOV X0, #5 ; X0 = 5

; Использование адресной константы

LDR X1, =num ; X1 = адрес num

; Загрузка значения из памяти

LDR X2, [X1] ; X2 = значение по адресу num = 10

; Использование ASCII-строки

LDR X3, =my_str ; X3 = адрес my_str

RET


Константы в ассемблере являются важным элементом программирования, позволяющим использовать фиксированные значения и адреса в программе. Понимание того, как правильно использовать и представлять константы, необходимо для написания эффективных и корректных программ на языке ассемблера.


Регистры

Регистром называют небольшую, очень быструю ячейку памяти, встроенную в процессор. Он хранит данные, которые процессор использует непосредственно при выполнении инструкций (операнды, адреса, флаги и т.п.).


ARM64 – 64‑битная архитектура, в которой основной набор регистров называется General‑Purpose Registers (GPR). Они обозначаются X0‑X30 (64‑битные) и их 32‑битные части – W0‑W30. Кроме них существуют специальные регистры, используемые системой, стеком и управлением процессором.


X0 (64 бита)


63 … 32 (старшие)

31 … 0 (младшие)


LSR x1, x0, #32

W0 (32 bits)


X0 – 64‑битный общий регистр процессора ARM64 (AArch64). Таким образом, X0 представляет собой 64‑битный контейнер, где младшие 32 бита образуют отдельный регистр W0, а старшие 32 бита доступны только в инструкции LSR x1, x0, #32.

bannerbanner