Опубликован: 19.01.2025 | Доступ: свободный | Студентов: 0 / 0 | Длительность: 02:34:00
Лекция 5:

Программирование на языке Assembler

< Лекция 4 || Лекция 5: 12345 || Лекция 6 >

Набор инструкций архитектуры (ISA) RISC-V, который обсуждался ранее, является основой для программирования на языке Assembler. Однако не очень удобно реализовывать программу исключительно на уровне ISA. По этой причине компилятор языка Assembler для RISC-V предоставляет различные псевдо-инструкции и директивы, которые облегчают разработку программ.

К концу этой лекции вы будете:

  • понимать и уметь использовать метки и директивы;
  • понимать и уметь использовать псевдо-инструкции;
  • знать, почему и как использовать бинарный интерфейс приложений (application binary interface, ABI);
  • уметь реализовывать поток выполнения программ.

Метки в языке Assembler

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

Под меткой понимается символьная последовательность, оканчивающаяся двоеточием. Также существует особая разновидность меток - числовые метки, - это локальные метки, которые могут быть заданы с суффиксами 'f' для перехода вперёд и 'b' для перехода назад.

Пример показан ниже:

1: 
    j 1b
    j 1f
1: 

Первый переход выполнится на метку 1 выше кода, потому что указан суффикс 'b', второй переход выполнится на метку 1 ниже кода, потому что указан суффикс 'f'.

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

Рассмотрим следующий пример:

    addi x1, x0, 1
    beq  x1,x0, there
    addi x1, x1, 1
there:
    addi x1, x1, 1

Компилятор переводит программу в машинные коды и вставляет смещения, ассоциируемые с метками:

0x0:        00100093        addi x1 x0 1
0x4:        00008463        beq x1 x0 8 <there>
0x8:        00108093        addi x1 x1 1

0000000c <there>:
0xc:        00108093        addi x1 x1 1

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

Если мы посмотрим на базовый набор RV32I ISA, можно увидеть, что адресация по всему адресному пространству может потребовать более одной инструкции: инструкции auipc и lui используются для загрузки старшей части непосредственного значения адреса в регистр, а для загрузки младшей части в регистр используется инструкция addi. Таким образом, требуется некий механизм разделения адресов меток на старшую и младшую составляющие.

Функции переразмещения %hi(symbol) и %lo(symbol) разделяют адрес метки на младшую и старшую части. Компоновщик (линковщик) размещает программу в памяти и назначает адреса соответствующим символам. Таким образом, код:

        lui  x1, %hi(there)         #absolute higher 20 bits
        addi x1, x1, %lo(there)     #absolute lower 12 bits
there:

транслируется в код:

0x10078:    lui  x1,0x10
0x1007c:    addi x1,x1,128 # 10080 <there>

Адрес метки there после размещения имеет адрес 0x10080. Функции %hi и %lo используются для задания корректных адресов в регистре x1.

Ещё одним примером функций переразмещения являются функции адресации, относительной значения программного счётчика. К ним относятся функции %pcrel_hi(symbol) и %pcrel_lo(label), которые работают вместе с инструкциями auipc и addi. Однако, поскольку адресация относительная, они используются отличным от глобальной адресации способом. Так, компилятор сгенерирует из такого кода:

1:  auipc x1, %pcrel_hi(there)#relative higher 20 bits
    addi  x1, x1, %pcrel_lo(1b)#relative lower 12 bits label 1 backwards
there:

следующий код:

1:
0x10078:	auipc x1,0x0
0x1007c:	addi x1,x1,8 # 10080 <there>

В этом примере также адрес метки there равен 0x10080. Инструкция auipc должна добавить 0x0 к значению программного счётчика 0x10078 для получения старшей части адреса метки 'there'. Результат инструкции auipc - число 0x10078 в регистре x1. Инструкция addi добавляет число 8 к регистру x1, поскольку 8 - это младшая часть разницы адресов меток there и 1.

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

Директивы языка Assembler

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

В таблице 4.1 ниже приводится перечень наиболее часто употребляемых директив:

Таблица 4.1.
Директива Аргументы Описание
.text Переключиться на секцию .text
.data Переключиться на секцию .data
.rodata Переключиться на секцию .rodata
.bss Переключиться на секцию .bss
.section .text, .data, .rodata, .bss Переключиться на секцию, задаваемую аргументом
.equ name, value определить имя для константного значения
.ascii "string" начало строки без терминального нуля
.asciz "string" начало строки с терминальным нулём
.string "string" то же самое, что и .asciz
.byte expression [,expression]* 8-битные значения, разделённые запятыми
.half expression [,expression]* 16-битные значения, разделённые запятыми
.word expression [,expression]* 32-битные значения, разделённые запятыми
.dword expression [,expression]* 64-битные значения, разделённые запятыми
.zero integer байты, заполненные нулями
.align integer значение, выровненное по заданной степени 2
.globl symbol_name внести symbol_name в таблицу символов
< Лекция 4 || Лекция 5: 12345 || Лекция 6 >