Целью работы является освоение способов компиляции программ на ассемблере RISC-V.
Для достижения поставленной цели требуется решить следующие задачи:
Презентация к блоку "Введение и инструменты сборки"
Язык ассемблера представляет максимально близкий к аппаратному обеспечению способ для создания прикладных и системных программ. В таких программах практически отсутствуют привычные абстракции и программист вынужден оперировать реальными аппаратными структурами процессора и ОЗУ, а также учитывать особенности аппаратной архитектуры конкретной системы. Это приводит к тому, что языки ассемблера для разных процессорных архитектур не совместимы между собой, однако, общая логика построения программ и манипуляций с данными сохраняется. Ниже мы поясним необходимые элементы простейшей ассемблерной программы, подробное введение в ассемблер для RISC-V дается в книге, синтаксис команд описан в мануале.
Ассемблерные программы представляют собой последовательность простейших команд для процессора, которые оперируют минимальными элементами данных и простейшими операциями с ними или другими устройствами. Для создания базового комфорта программисту, в большинстве языков ассемблера (в том числе и для RISC-V) предусмотрены такие структурные единицы как псевдооператоры и метки:
- .global (задает точку входа) и .data (задает секцию данных). Это не единственное назначение псевдооператоров, подробнее можно прочитать в мануале.В отличии от языков программирования высокого уровня, в ассемблере вместо переменных используются регистры - небольшие ячейки памяти, расположенные на микросхеме процессора. В каждой архитектуре процессора свой ограниченный набор регистров. Малый объем памяти, отводимый на регистры, компенсируется высокой скоростью работы с ними - она превосходит аналогичный показатель для ОЗУ и ПЗУ. Регистры могут иметь специальное назначение - так в примере простейшей программы ниже мы будем использовать четыре регистра (a0, a1, a2, a7), назначение которых подразумевает передачу аргументов для вызываемой функции системного вызова. В остальном работа с регистрами с некоторыми оговорками похожа на работу с переменными в языках программирования высокого уровня.
Управляющие инструкции в языке ассемблера выглядят очень просто по сравнению с командами и ключевыми словами языков высокого уровня. Как правило, команда представляет собой сокращение полного английского наименования совершаемого действия:
la - Load Address (загрузить адрес в регистр),addi - Add an Immediate value to register (сложить константу со значением из регистра),ecall - Environment Call (произвести вызов системной процедуры). В случае ОС Linux (в данном случае RISC-V ОС) это будет означать вызов функции стандартной библиотеки.Команды могут иметь от нуля до трех операндов (аргументов) для базового набора инструкций. В расширениях RISC-V может встречаться и большее число операндов.
la - два операнда (регистр назначения и адрес области данных),addi - три операнда (регистр назначения для результата сложения, слагаемое №1, слагаемое №2),ecall - без операндов.Простейший helloworld на ассемблере для RISC-V будет выглядеть так:
.global _start # Точка входа для программы
# Настройка вызова функции write так, чтобы он вывел строчку в терминал
# В регистры a0-a2 записываются аргументы write
# В регистре a7 указывается номер write
_start: addi a0, x0, 1 # 1 = Поток стандартного выовда (StdOut)
la a1, helloworld # Загрузка адреса контсанты helloworld в a1
addi a2, x0, 13 # Запись длины строки в a2
addi a7, x0, 64 # Запись номера функции write 64
ecall # Выполнение функции write
# Настройка вызова exit и вызов функции
addi a0, x0, 0 # Указание кода возврата для exit
addi a7, x0, 93 # Запись номера функции exit 93
ecall # Выполнение функции exit
.data
helloworld: .ascii "Hello World!\n"
Необходимо отдельно рассказать о разнице между системным вызовом exit и одноименной функцией стандартной библиотеки:
exit_group.exit(), _Exit(), _exit() используют именно вызов exit_group.Для компиляции и запуска нам потребуется выполнить следующие команды в терминале RISC-V ОС (предполагаем, что исходный код сохранен в файле hello.s):
$ as -o hello.o hello.s # Запуск компилятора ассемблерных программ $ ld -o hello hello.o # Запуск линковщика $ ./hello
В случае успеха, в терминал будет выведена строка "Hello World!".
Аналогичные операции можно проделать и из гостевой ОС, выполняя кросс-компиляцию, при этом потребуется использовать утилиты riscv64-linux-gnu-as и riscv64-linux-gnu-ld соответственно.
Для исследования скомпилированных ассемблерных программ удобно использовать утилиту objdump. Она позволяет выполнить обратное преобразование - из бинарного файла получить код на языке ассемблера:
$ objdump -d ./hello
Флаг -d в данном случае обозначает "дизассемблировать секции файла, в которых ожидается наличие кода". Результат выполнения:
./hello: file format elf64-littleriscv Disassembly of section .text: 00000000000100e8 <_start>: 100e8: 00100513 li a0,1 100ec: 00001597 auipc a1,0x1 100f0: 02058593 addi a1,a1,32 # 1110c <__DATA_BEGIN__> 100f4: 00d00613 li a2,13 100f8: 04000893 li a7,64 100fc: 00000073 ecall 10100: 00000513 li a0,0 10104: 05d00893 li a7,93 10108: 00000073 ecall
Вывод objdump имеет следующую структуру:
hex-код операции код операции ассемблерный код
В структуре восстановленного ассемблерного кода угадывается структура исходной программы, однако очевидно, что операции указанные нами изначально были преобразованы компилятором.
Подготовьте программу, выполняющую с помощью языка ассемблера следующую задачу:
Для вывода изображения используйте несколько последовательных вызовов write.
Программу необходимо скомпилировать и убедится в ее работоспособности на RISC-V ОС. С помощью утилиты objdump (в RISC-V ОС) необходимо дизассемблировать бинарный файл программы и зафиксировать в отчете общее количество строк, а также hex-код последней команды.
Для выполнения поставленной задачи необходимо понять, как выглядит текст, выводимый вашей программой и фигура, которую вам необходимо нарисовать в вашей программе. Для определенности будем считать, что это квадрат из символов *, размером 2х2. Для наглядности лучше всего изобразить эту фигуру:
** **
Далее необходимо составить программу, которая будет выводить указанные строки. Учитывая пример фигуры выше, всего программа должна вывести три строки. Запишите эти строки в секцию .data. Соответственно, необходимо сделать три вызова write.
Далее необходимо скомпилировать и запустить программу в RISC-V ОС. Для этого можно использовать утилиты as и ld, либо применить кросс-компиляцию и запуск из гостевой ОС. После компиляции и запуска, программу необходимо проанализровать через objdump.
Напишите программу, которая выводит две строки "I am an example string\n" и "Another string example\n".
.global _start
_start: addi a0, x0, 1
la a1, string1
addi a2, x0, 25
addi a7, x0, 64
ecall
addi a0, x0, 1
la a1, string2
addi a2, x0, 25
addi a7, x0, 64
ecall
addi a0, x0, 0
addi a7, x0, 93
ecall
.data
string1: .ascii "I am example string\n"
string2: .ascii "Another string example\n"
ld из пакета binutils?li и auipc?.global?Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.