Адресация относительно регистра PC

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

Иногда программам может потребоваться доступ к данным, адрес которых известен относительно текущего счетчика программв - регистра PC (Program Counter). Распространенным примером подобного вида адресации является чтение данных из литерального пула. Пулы литералов часто используются компиляторами и ассемблерами для хранения некоторых постоянных данных в конце блока кода. Поскольку расстояние между литералом и инструкцией, которая обращается к нему, фиксировано, его можно загрузить через адрес инструкции плюс некоторое фиксированное смещение. Поскольку адрес, к которому осуществляется доступ, вычисляется относительно адреса текущей инструкции (а он хранится в регистре PC), то данный тип адресации и называется адресацией относительно PC.

Другой распространенной ситуаций использования этого вида адресации представляет обращение к адресу глобальной переменной. В этом случае ассемблер может вычислить смещение от текущей инструкции (которая будет в регистре PC при выполнении инструкции) до адреса метки. А инструкции загрузки адреса этой метки обычно неявно преобразуются ассемблером в инструкции загрузки относительно PC.

Для загрузки константного значения или метки инструкция LDR использует специальный синтаксис

LDR Xn, =label

Загрузка меток

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

.global _start 
_start:
    ldr x1, =num      // загружаем в X1 адрес метки num
    ldr x0, [x1]     // Х0=22

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

.data
    num: .quad 22

В данном случае в регистр Х1 загружаем адрес метки (глобальной переменной) num.

Причем таким образом мы можем загружать адрес вообще любой метки. Например:

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

Здесь также определена метка exit, по адресу которой располагается инструкция mov x8, #93.

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

Подобный синтаксис может быть полезен, когда надо загрузить константу, однако она слишком большая (больше 12 байт), чтобы быть загруженной в регистр с помощью инструкции MOV. Например, если мы попробуем в рамках одной инструкции MOV поместить в регистр число 0x1234cdef, то на этапе компиляции мы столкнемся с ошибкой, которая говорит, что нельзя скопировать непосредственый операнд одной инструкцией. Однако мы можем ее загрузить ее в рамках одной инструкции LDR

.global _start 
_start:
    ldr x0, =0x1234cdef     // Х0=0x1234cdef 

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

Таким образом, в регистре Х0 окажется число 0x1234cdef. Если мы посмотрим на вывод дизассемблера утилиты objdump, то мы увидим, что для литерала 0x1234cdef создается специальное поле после кода программы:

Disassembly of section .text:

0000000000000000 <_start>:
   0:   58000080        ldr     x0, 10 <_start+0x10>
   4:   d2800ba8        mov     x8, #0x5d                       // #93
   8:   d4000001        svc     #0x0
   c:   00000000        udf     #0
  10:   1234cdef        and     w15, w15, #0xf0f0f0f0
  14:   00000000        udf     #0

Здесь мы видим, что непосредственно сам литерал 0x1234cdef хранится в программе по относительному адресу 10. Дизассемблер может попытаться рассматривать это число как код инструкции, например, в случае выше дизассемблер некорректно транслировал это число в инструкцию and w15, w15, #0xf0f0f0f0. Но в реальности это просто числовой литерал. И инструкция LDR в начале программы загружает этот литерал, используя его относительный адрес <_start+0x10>.

Причем ассемблер может сгруппировать литералы и отбросить дубликаты. Например:

.global _start 
_start:
    ldr x0, =0x1234cdef
    ldr x1, =0x1234cdef
    ldr x2, =0x1234ffff

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

Здесь в регистры Х0 и Х1 загружается одно и то же число - 0x1234cdef, а в Х2 - число 0x1234ffff. Если мы посмотрим на вывод дизассемблера:

Disassembly of section .text:

0000000000000000 <_start>:
   0:   580000c0        ldr     x0, 18 <_start+0x18>
   4:   580000a1        ldr     x1, 18 <_start+0x18>
   8:   580000c2        ldr     x2, 20 <_start+0x20>
   c:   d2800ba8        mov     x8, #0x5d                       // #93
  10:   d4000001        svc     #0x0
  14:   00000000        udf     #0
  18:   1234cdef        and     w15, w15, #0xf0f0f0f0
  1c:   00000000        udf     #0
  20:   1234ffff        .inst   0x1234ffff ; undefined
  24:   00000000        udf     #0

то мы увидим, что в конце программы сохраняются два литерала - это и есть пул литералов (literal pool). При этом дубликаты отсутствуют.

Загрузка относительно смещения

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

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

.data
    num1: .quad 12
    num2: .quad 22

Здесь в регистр X0 будут загружаться данные, которые располагаются на 12 байт от текущего (от адреса данной инструкции LDR). Каждая инструкция имеет 32 бита или 4 байта. Всего в программе до секции .data 3 инструкции, то есть как минимум 3 * 4 = 12 байт. Это в общем случае, потому что в реальности ассемблер может добавлять дополнительные нулевые байты. Но в данном частном случае после 12 байт будет располагаться секция .data и, в частности, переменная num1, в которой определяются 8-байтовое число 12. Поэтому в регистр X0 будет загружено число 12.

Стоит отметить, что загружается именно само значение, а не адрес.

Если мы перейдем на 20 байта от адреса инструкции:

ldr x0, #20 

то в регистр X0 будет загружено число num2.

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