Программирование на языке Assembler
Набор инструкций архитектуры (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 ниже приводится перечень наиболее часто употребляемых директив:
Директива | Аргументы | Описание |
---|---|---|
.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 в таблицу символов |