Типы адресации. Адресация со смещением

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

Для обращения к данным по определенному адресу мы можем использовать один из способов адресации:

  • Обращение по адресу в базовом регистре.

    LDR Xt, [Xn]

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

  • Адресация со смещением

    LDR Xt, [Xn, <offset>]

    Здесь <offset> представляет смещение относительно адреса в базовом регистре Xn. Смещение может представляет константное значение или динамически вычисляемое значение. Оно может быть положительным и отрицательным. Фактический адрес, к которому будет идти обращение, будет равен адресу в базовом регистре плюс смещение.

  • Преиндексная адресация

    LDR Xt, [Xn, <offset>]!

    При преиндексной адресации сначала изменяется адрес в базовом регистре: к нему прибавляется смещение. То есть Xn = Xn + <offset>. И именно по этому адресу будет идти обращение. То есть в отличие от предыдущего виде адресации здесь происходит изменение адреса в базовом регистре.

  • Постиндексная адресация

    LDR Xt, [Xn], <offset>

    При постиндексной адресации сначала идет обращение по адресу в базом регистре (получение или сохранение данных), затем обновляется адрес в базовом регистре - к нему прибавляется смещение. То есть в отличие от предыдущего вида адресации здесь обращение к памяти идет до изменения адреса в базовом регистре.

  • Обращение по адресу относительно регистра PC

Рассмотрим сначала адресацию со смещением.

Адресация со смещением

При данном типе адресации для получения фактического адреса к адресу базового регистра прибавляется смещение в байтах. В качестве спещения могут применяться:

  • Положительные и отрицательные константы - непосредственные операнды

    LDR Xt, [Xn, #imm]
  • Регистры

    LDR Xt, [Xn, Xm]
    LDR Xt, [Xn, Wm]
  • Регистры с применением сдвига или расширения знаком/нулем

    LDR Xt, [Xn, Xm, <оператор_сдвига> #imm]
    LDR Xt, [Xn, Wm, <оператор_расширения> {#imm}]
    

Смещение - непосредственный операнд

Если в качестве смещения применяется непосредственный операнд, то он должен быть размером не более 12 бит. Для инструкций LDR/STR, если первый операнд - 32-разрядный регистр W0-W30, то смещение должно быть кратно 4, а если первый операнд - 64-разрядный регистр Х0-Х30, то смещение должно быть кратно 8. При этом смещение долдно быть в диапазоне 0-4095.

Используем непосредственый операнд в качестве смещения:

.global _start 
_start:
    ldr x1, =nums      // загружаем в X1 адрес nums
    ldr x0, [x1, 16]     // смещение равно 16 байтам, Х0=13

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

.data
    nums: .quad 11, 12, 13, 14, 15, 16, 17, 18

Здесь в регистр Х1 загружен адрес массива nums, который состоит их 8-байтных чисел. Далее обращаемся по адресу на 16 байт вперед относительно адреса в регистре Х1:

ldr x0, [x1, 16]     // смещение равно 16 байтам, Х0=13

Регистр Х1 хранит адрес массива nums, точнее самого первого его элемента (число 11). Прибавив к этому адресу 16 байт, получим адрес второго элемента в массиве nums - числа 13. И именно это число будет загружено в регистр Х0.

Смещения могут представлять вычисляемые выражения. Например:

.global _start 
_start:
    ldr x1, =nums      // загружаем в X1 адрес nums
    ldr x0, [x1, 3*8]     // смещение равно 24 байтам, Х0=14

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

.data
    nums: .quad 11, 12, 13, 14, 15, 16, 17, 18

В данном случае обращаемся к 4-му элементу (элементу с индексом 3 или nums[3]). Поскольку каждый элемент занимает 8 байт, то смещение представляет результат выражения 3*8 (то есть 24 байта). Использование таких выражений может быть проще в плане наглядности, так как мы видим и индекс элемента - 3 и размер элемента - 8.

Если смещение отрицательное, то фактически происходит вычитание смещения от адреса из базового регистра, то есть мы как-бы двигаемся в памяти назад. Например:

.global _start 
_start:
    ldr x1, =nums      // загружаем в X1 адрес nums
    ldr x0, [x1, -8]     // смещение равно 8 байтам, Х0=125

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

.data
    number: .quad 125
    nums: .quad 11, 12, 13, 14, 15, 16, 17, 18

Здесь смещение равно -8, то есть относительно адреса переменной nums смещаемся назад на 8 байт. А по этому адресу располагается 8-байтная переменная number, равная 125. Поэтому в регистре Х0 будет загружено число 125.

LDUR

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

Disassembly of section .text:

0000000000000000 <_start>:
   0:   58000081        ldr     x1, 10 <_start+0x10>
   4:   f85f8020        ldur    x0, [x1, #-8]
   8:   d2800ba8        mov     x8, #0x5d                       // #93
   c:   d4000001        svc     #0x0

Здесь мы видим, что вместо второй инструкции LDR используется инструкция LDUR. Эта инструкция во многоим аналогична LDR, только может принимать смещения, которые не являются кратными 4 или 8. Однако в этом случае смещения должны быть не более 9 бит и находиться в диапазоне от -256 до 255. Существует набор подобных инструкций, которые принимают смещения, не кратные 4 и 8:

  • LDUR: загружает данные размером целевого регистра (аналог LDR)

  • LDURB: загружает 1 байт (аналог LDRB)

  • LDURSB: загружает 1 байт со знаком (аналог LDRS)

  • LDURH: загружает полслова - 2 байта (аналог LDRH)

  • LDURSH: загружает полслова со знаком (аналог LDRSH)

  • LDURW: загружает слово - 4 байта (аналог LDRW)

  • LDURSW: загружает слово со знаком (аналог LDRSW)

  • STUR: сохраняет данные размером целевого регистра (аналог STR)

  • STURB: сохраняет 1 байт (аналог STRB)

  • STURH: сохраняет полслова (аналог STRH)

Все эти инструкции принимают смещение в диапазоне от -256 до 255. И если инструкциям LDR/STR передается или отрицательное смещение или смещение не кратное 4 или 8, то дизассемблер транслирует их в соответствующие инструкции LDUR/STUR

Регистр в качестве смещения

В качестве смещения также может применяться значение регистра:

LDR Wt, [Xn, Xm]
LDR Xt, [Xn, Xm]

В таком случае регистр часто выступает в качетсве индекса для обращения к каким-то частям данным. Например:

.global _start 
_start:
    mov x2, 2           // Х2-регистр-индекс - обращаемся к nums[2]
    ldr x1, =nums      // загружаем в X1 адрес nums
    ldrb w0, [x1, x2]     // эквивалентно ldr x0, [x1 + x2]

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

.data
    nums: .byte 11, 12, 13, 14, 15, 16, 17, 18

Здесь определен массив однобайтовых чисел nums. Допустим, мы хотим обратиться к третьему его элемента (nums[2]), то есть к числу 13 в данном случае. Каждое число в массиве nums находится друг от друга на расстоянии 1 байта (так как числа однобайтовые). Соответственно чтобы получить адрес тертьего элемента, надо к адресу начала массива nums (адресу его первого элемента) прибавить 2 байта.

В качестве регистра-индекса выступает регистр X2 - в него загружаем индекс искомого элемента:

mov x2, 2           // Х2-регистр-индекс - обращаемся к nums[2]
ldr x1, =nums      // загружаем в X1 адрес nums

В регистр Х1 загружаем адрес массива nums, то есть регистр Х1 будет базовым регистром. Затем используем индекс в регистре Х2 и базовый адрес в регистре Х1, с помощью инструкции ldrb (нам нужно получить 1 байт) обращаемся к элементу по нужному адресу:

ldrb w0, [x1, x2]     // эквивалентно ldr x0, [x1 + x2]

Регистр-смещение со сдвигом

К регистру-смещению можно применять стандартные операции сдвига - LSL/LSR:

LDR Wt, [Xn, Xm, LSL #2]    // адрес = Xn + (Xm*4)
LDR Xt, [Xn, Xm, LSL #3]    // адрес = Xn + (Xm*8)

Если регистру-смещение представляет 32-разрядный регистр W0-W30, то также к нему можно применять операции расширения знаком и нулем UXTW/SXTW/SXTX:

LDR Wt|Xt, [Xn, Wm, UXTW {#imm}]
LDR Wt|Xt, [Xn, Wm, SXTW {#imm}]
LDR Wt|Xt, [Xn, Wm, SXTX {#imm}]

В качестве примера рассмотрим следующую программу:

.global _start 
_start:
    mov x2, 3           // Х2-регистр-индекс - обращаемся к nums[3]
    ldr x1, =nums      // загружаем в X1 адрес nums
    ldr x0, [x1, x2, lsl 3]     // эквивалентно ldr x0, [x1, x2*8]

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

.data
    nums: .quad 11, 12, 13, 14, 15, 16, 17, 18

В секции данных определен массив nums, который содержит числа размером 8 байт. То есть каждое последующее число находится от предыдущего на расстоянии 8 байт. Допустим, мы хотим получить четвертое число в этом массиве (в данном случае число 14). Оно будет находится от начала массива на расстоянии 3 * 8 = 24 байта. То есть если к адресу массива nums прибавить 24 байта, то мы получим адрес nums[3], то есть четвертого числа.

Для получения нужного нам числа сначала помещаем в регистр Х2 индекс числа - 3:

mov x2, 3           // Х2-регистр-индекс - обращаемся к nums[3]
ldr x1, =nums      // загружаем в X1 адрес nums

То есть регистр Х2 будет выполнять роль смещения. Затем загружаем в регистр Х1 адрес массива nums. Таким образом, Х1 будет выполнять роль базового регистра. Далее, используя сдвиг смещения, получаем нужный нам элемент:

ldr x0, [x1, x2, lsl 3]

Сдвиг влево на 3 эквивалентен умножению на 8. То есть финальный адрес будет равен x1 + x2*8.

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