Ранее мы рассмотрели, что для получения адреса метки применяется инструкция LDR, которой через знак "равно" (=) передается название метки:
LDR Xn, =label
Также получуть адрес можно с помощью инструкции ADR. Она принимает два операнда - регистр и метка, адрес которой будет загружаться в регистр:
ADR Xn, label
Например:
.global _start _start: mov X0, #1 adr X1, hello // загружаем в X1 адрес строки hello mov X2, #18 mov X8, #64 svc 0 mov X0, 0 mov X8, #93 svc 0 .data hello: .ascii "Hello METANIT.COM\n" // данные для вывода
Здесь в регистр X1 помещается адрес строки hello (адрес первого ее символа).
Используя синтаксис косвенной адресации, мы можем получить непосредственное содержимое по этому адресу:
.global _start _start: adr X1, hello ldr X0, [X1] // в X0 данные из адреса в X1 - символ "H" mov X8, #93 svc 0 .data hello: .ascii "Hello METANIT.COM\n"
Здесь в X1 загружается адрес строки hello, а в X0 помещаются те данные, которые располагаются по этому адресу. Поскольку адрес строки - это адрес первого ее символа, то в X1 помещается числовой код символа "H", то есть число 72.
Возможность получения адресов позволяет нам манипулировать адресами, производить с ними различные операции. Например:
.global _start _start: mov X0, #1 // 1 = StdOut - поток вывода adr X1, hellometanit // строка для вывода на экран adr X2, helloworld sub X2, X2, X1 // длина строки - X2 = X2-X1 mov X8, #64 svc 0 mov X0, X2 // в X0 данные из адреса в X1 - символ "H" mov X8, #93 svc 0 .data hellometanit: .ascii "Hello METANIT.COM\n" helloworld: .ascii "Hello World\n"
Здесь у нас две строки. Поскольку данные в секции .data
располагаются последовательно, то чтобы найти количество символов (байтов) первой строки,
нам надо от адреса второй строки отнять адрес первой строки:
adr X1, hellometanit // строка для вывода на экран adr X2, helloworld sub X2, X2, X1 // длина строки - X2 = X2-X1
Или, к примеру, переместимся в строке на 1 символ вперед:
adr X1, hellometanit add X1, X1, #1
Здесь к адресу в X1 прибавляется 1, то есть адрес смещается вперед на 1 байт, а в X1 окажется адрес второго символа.
Символ точки . в ассемблере представляет специальную переменную, которая содержит текущий адрес. И с этим адресом можно производить все те же операции, что и с обычным адресом. Например, можно поместить текущий адрес инструкции в регистр:
MOV X4, .
То есть в регистре X4 будет храниться адрес текущей инструкции.
Можно прибавлять или вычитать определенное количество байт, получая новые адреса. Например:
. - 5 // на 16 байт от текущего адреса назад . + 5 // на 8 байт от текущего адреса вперед
В ряде ситуаций это может упростить программу. Например, возьмем следующую программу:
.global _start _start: // вывод строки на консоль mov x0, #1 // 1 - стандартный поток вывода ldr x1, =message // загружаем адрес выводимой строки message mov x2, count // в регистр x2 - количество символов mov x8, #64 // функция Linux для вывода в поток svc 0 // вызываем функцию Linux // выход из программы mov x0, count // для теста в качестве кода возврата помещаем в Х0 количество символов mov x8, #93 // устанавливаем функцию Linux для выхода из программы svc 0 // Вызываем функцию Linux .data message: .ascii "Hello METANIT.COM!\n" .equ count, .-message
Чтобы вывести строку на консоль с помощью системного вызова Linux с номером 64, нам нужно передать количество символов в регистр X2. В программе выше количество символов это определяется динамически с помощью выражения
.equ count, .-message
Директива .equ определяет константу count
, значение которой равно текущий_адрес
- адрес_метки_message
.
Поскольку в данном случае 1 символ эквивалентен 1 байту, то count
будет содержать количество символов.
Чтобы поместить констату в регистр, применяется инструкция mov
:
mov x2, count
Таким образом, на консоль будет выведено
Hello METANIT.COM! 19
где 19 будет представлять количество символов.
Подобные динамические значения можно присваивать и переменным. Правда, конкретно в случае с подсчетом длины строки лучше использовать констату, потому что в случае с переменной
нам нужно учитывать выравнивание по 4 байтам, которое требуется для загрузки переменной с помощью инструкции ldr
:
.global _start _start: // вывод строки на консоль mov x0, #1 // 1 - стандартный поток вывода ldr x1, =message // загружаем адрес выводимой строки message ldr x2, count // загружаем переменную count mov x8, #64 // функция Linux для вывода в поток svc 0 // вызываем функцию Linux // выход из программы mov x8, #93 // устанавливаем функцию Linux для выхода из программы svc 0 // Вызываем функцию Linux .data message: .ascii "Hello METANIT.COM!\n" .align 2 // из-за выравнивания размер переменная count будет некорректной count: .quad .-message
Здесь в строке message 19 символов, то есть 19 байт. Для загрузки переменной в регистр с помощью инструкции ldr
требуется выравнивание по 4 байтам. Соответственно строка
message фактически будет занимать 20 байт. А переменная count будет хранить число 20. Хотя строка корректно будет выведена на консоль, но само значение count будет некорректным,
что может негативно сказаться на каких-то других вычислениях.