Инструкция LDR. Загрузка данных

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

Для загрузки данных применяется инструкция LDR. Она загружает в регистр адрес данных и затем использует этот адрес для загрузки в регистр реальных данных. Одна из ее форм:

LDR Xd, label

Здесь Xd - регистр, в который загружается адрес, а label - метка (например, глобальная переменная), данные по адресу которой загружаются.

Простейший пример с загрузкой числа:

.global _start 
_start:
    ldr x0, number     // загружаем в X0 число number
// выход из программы
    mov x8, #93       // устанавливаем функцию Linux для выхода из программы
    svc 0             // Вызываем функцию Linux

.data
    number: .word 15      // определяем слово, которое равно 15

В данном случае в регистр X0 загружается число number, то есть число 15.

Подобным образом можно загружать и другие данные. Например, загрузим байт:

.global _start 
_start:
    ldr x0, num     // X0 = 11
    mov x8, #93       // устанавливаем функцию Linux для выхода из программы
    svc 0             // Вызываем функцию Linux

.data
    num: .byte 11

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

.global _start 
_start:
    ldr x0, num2     // ! Ошибка

    mov x8, #93       // устанавливаем функцию Linux для выхода из программы
    svc 0             // Вызываем функцию Linux

.data
    num1: .byte 11
    num2: .byte 12
    num3: .byte 13
    num4: .byte 14

Здесь мы пытаемся загрузить в регистр Х0 число num2. По умолчанию секция .data выравнивается по четной границе, то есть кратной 2. Поэтому число num2 располагается по нечетному адресу. Чтобы все-таки загрузить число num2 в регистр Х0, надо установить выравнивание по границе, кратной 2 (то есть как минимум по 2)

.global _start 
_start:
    ldr x0, num2     // X0 = 12

    mov x8, #93       // устанавливаем функцию Linux для выхода из программы
    svc 0             // Вызываем функцию Linux

.data
    num1: .byte 11
    .align 2        // теперь num2 выравнено по 2 байтам
    num2: .byte 12
    .align 2
    num3: .byte 13
    .align 2
    num4: .byte 14

LDR и загрузка данных по адресу

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

Для получения адреса метки применяется следующий синтаксис:

LDR Xn, =label

После знака "равно" (=) указывается метка, адрес которой мы хотим получить.

Далее для загрузки данных, которые располагаются по определенному адресу, применяется следующая форма инструкции LDR :

LDR{type} Xt, [Xa]

Для обращения по адресу, который хранится в регистре, используются квадратные скобки - [Xa]. Регистр Xa еще называется базовым регистром, потому что он хранит базовый адрес, по которому будет идти обращение. Справа от названия инструкции опционально может указываться суффикс type, который уточняет тип загружаемых данных и который может принимать следующие значения

  • B: беззнаковый байт

  • SB: байт со знаком

  • H: беззнаковые полслова (16 бит)

  • SH: полслова со знаком (16 бит)

Например, загрузим число:

.global _start 
_start:
    ldr x1, =num      // загружаем адрес метки num в Х1
    ldr x0, [x1]      // загружаем данные по адресу из Х1 в регистр Х0
    mov x8, #93       // устанавливаем функцию Linux для выхода из программы
    svc 0             // Вызываем функцию Linux

.data
    num: .quad 11

Таким образом, загрузка переменной num из секции .data разбивается на два этапа. Сначала в регистр X1 загружается адрес метки num:

LDR X1, =num

Затем в регистр X0 загружаем собственно данные, которые хранятся по адресу в X1.

LDR X0, [X1]

Квадратные скобки [X1] указывают на непрямой доступ к памяти. Это означает загрузку данных, сохраненных по адресу, на который указывает регистр X1. То есть в X2 загружаются НЕ данные регистра X1, а данные, на которые указывает регистр X1.

Стоит учитывать, что инструкция LDR загружает данные размером, равным разрядности регистра. То есть в примере выше инструкция будет пытаться загрузить по адресу из [X1] 64 байта. Это может приводить к избыточной загрузке данных. Например:

.global _start 
_start:
    ldr x1, =n1     // загружаем в X1 адрес числа n1
    ldr x0, [x1]     // загружаем в X0 значение, которое находится по адресу из X1

    mov x8, #93       // устанавливаем функцию Linux для выхода из программы
    svc 0             // Вызываем функцию Linux

.data
    n1: .byte 1
    n2: .byte 2
    n3: .byte 3
    n4: .byte 4

Здесь в регистр X1 загружается адрес числа n1. Далее загружаем в регистр X0 данные по адресу из X1, то есть по сути само число n1. Но число n1 представляет собой 1 байт, а регистр X0 - 64-разрядный, поэтому инструкция LDR будет пытаться загрузить 8 байт. Соответственно в регистр X0 будут загрузжены значения всех четырех однобайтных переменных n1, n2, n3, n4. В итоге в регистре X0 будет число 0x0000000004030201 - то есть все числа будут последовательно располагаться в регистре.

В каких-то ситуациях это может быть преимуществом. Например, нам надо загрузить массив из 8 или менее однобайтовых чисел. И мы можем разом загрузить его в регистр:

.global _start 
_start:
    ldr x1, =nums     // загружаем в X1 адрес числа nums
    ldr x0, [x1]     // X0 = 0x0807060504030201

    mov x8, #93       // устанавливаем функцию Linux для выхода из программы
    svc 0             // Вызываем функцию Linux

.data
    nums: .byte 1, 2, 3, 4, 5, 6, 7, 8

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

  • LDRB: загрузка беззнакового байта

  • LDRSB: загрузка байта со знаком

  • LDRH: загрузка беззнаковых полслова (16 бит)

  • LDRSH: загрузка полслова со знаком (16 бит)

Эти инструкции загружают число в 32-х разрядный регистр W0-W30. S-версия (signed/со знаком) при загрузке данных будет распространять знак на остальную часть регистра.

Например, нам надо загрузить всего лишь один байт, и для этого применим инструкцию LDRB:

.global _start 
_start:
    ldr x1, =n1     // загружаем в X1 адрес числа n1
    ldrb w0, [x1]     // X0 = 0x01 - загружаем только 1 байт

    mov x8, #93       // устанавливаем функцию Linux для выхода из программы
    svc 0             // Вызываем функцию Linux

.data
    n1: .byte 1
    n2: .byte 2
    n3: .byte 3
    n4: .byte 4

Для загрузки 32 бит инструкции LDR в качестве первого операнда передается 32-разрядный регистр W0-W30:

ldr w0, [x1]

Загрузка строк

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

.global _start

_start: 
// вывод строки на консоль
    MOV X0, #1          // 1 = StdOut - стандартный поток вывода
    LDR X1, =message    // помещаем в регистр X1 адрес метки message
    MOV X2, #18         // длина строки
    MOV X8, #64         // функция Linux для вывода в поток
    SVC 0               // вызываем функцию Linux

// завершение программы
    MOV X0, #0          // помещаем в регистр X0  число 1
    MOV X8, #93         // номер функции Linux для выхода из программы - 93
    SVC 0               // вызываем функцию и выходим из программы

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

Здесь инструкция LDR загружает в регистр X1 адрес строки "Hello METANIT.COM\n". В данном случае используем системную функцию 64 в Linux, которая пишет в поток переданные данне. В регистр X0 передается номер стандартного потока вывода (для печати на консоль) - число 1. Кроме того, в регистр X2 помещается длина строки в символах. Если все символы представляют латинские символы, то каждый символ расценивается как однобайтный символ. Если используется кириллица, то каждый символ идет как двухбайтный символ. В примере выше все символы латинские, соответственно длина строки - 18. В итоге при выполнении программы на консоль будет выведена строка "Hello METANIT.COM"

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850