ЛР16. Рефактор методички (#97)

* ЛР16. Рефактор методички

* Apply suggestions from code review
This commit is contained in:
Andrei Solodovnikov
2024-07-29 16:15:48 +03:00
committed by GitHub
parent 59510a522b
commit 73e521687e
4 changed files with 115 additions and 106 deletions

View File

@@ -8,18 +8,28 @@
See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
* ------------------------------------------------------------------------------
*/
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
ENTRY(_start) /* мы сообщаем компоновщику, что первая
исполняемая процессором инструкция
находится у метки "start"
находится у метки "_start"
*/
_text_size = 0x10000; /* Размер памяти инстр.: 16KiB */
_data_base_addr = _text_size; /* Стартовый адрес секции данных */
_data_size = 0x04000; /* Размер памяти данных: 16KiB */
_data_end = _data_base_addr + _data_size;
/*
В данном разделе указывается структура памяти:
Сперва идет регион "instr_mem", являющийся памятью с исполняемым кодом
(об этом говорит аттрибут 'x'). Этот регион начинается
с адреса 0x00000000 и занимает 64 кибибайта.
Далее идет регион "data_mem", начинающийся с адреса 0x00000000 и занимающий
16 кибибайт. Этот регион является памятью, противоположной региону "instr_mem"
(в том смысле, что это не память с исполняемым кодом).
*/
MEMORY
{
instr_mem (x) : ORIGIN = 0x00000000, LENGTH = 64K
data_mem (!x) : ORIGIN = 0x00000000, LENGTH = 16K
}
_trap_stack_size = 2560; /* Размер стека обработчика перехватов.
Данный размер позволяет выполнить
@@ -29,25 +39,9 @@ _trap_stack_size = 2560; /* Размер стека обрабо
_stack_size = 1280; /* Размер программного стека.
Данный размер позволяет выполнить
до 16 вложенных вызовов.
от 16 вложенных вызовов.
*/
/*
В данном разделе указывается структура памяти:
Сперва идет регион "rom", являющийся read-only памятью с исполняемым кодом
(об этом говорят атрибуты 'r' и 'x' соответственно). Этот регион начинается
с адреса 0x00000000 и занимает _text_size байт.
Далее идет регион "ram", начинающийся с адреса _data_base_addr и занимающий
_data_size байт. Этот регион является памятью, противоположной региону "ram"
(в том смысле, что это не read-only память с исполняемым кодом).
*/
MEMORY
{
rom (x) : ORIGIN = 0x00000000, LENGTH = _text_size
ram (!x) : ORIGIN = _data_base_addr, LENGTH = _data_size
}
/*
В данном разделе описывается размещение программы в памяти.
Программа разделяется на различные секции:
@@ -59,7 +53,7 @@ MEMORY
SECTIONS
{
PROVIDE( _start = 0x00000000 ); /* Позиция start в памяти
/*
В скриптах компоновщика есть внутренняя переменная, записываемая как '.'
Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в
@@ -81,32 +75,31 @@ SECTIONS
секций, начинающихся на .text во всех переданных компоновщику двоичных
файлах.
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "rom".
регионе "instr_mem".
*/
.text : {*(.boot) *(.text*)} >rom
.text : {
PROVIDE(_start = .);
*(.boot)
*(.text*)
} > instr_mem
/*
Поскольку мы не знаем суммарного размера получившейся секции, мы проверяем
что не вышли за границы памяти инструкций и переносим счетчик адресов за
пределы памяти инструкций в область памяти данных.
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "ram".
Секция данных размещается аналогично секции инструкций за исключением
адреса загрузки в памяти (Load Memory Address, LMA). Поскольку память
инструкций и данных физически разделены, у них есть пересекающееся адресное
пространство, которое мы бы хотели использовать (поэтому в разделе MEMORY мы
указали что стартовые адреса обоих памятей равны нулю). Однако компоновщику
это не нравится, ведь как он будет размещать две разные секции в одно и то же
место. Поэтому мы ему сообщаем, с помощью оператора "AT", что загружать секцию
данных нужно на самом деле не по нулевому адресу, а по какому-то другому,
заведомо большему чем размер памяти инструкций, но процессор будет
использовать адреса, начинающиеся с нуля. Такой вариант компоновщика
устраивает и он собирает исполняемый файл без ошибок. Наша же задача,
загрузить итоговую секцию данных по нулевым адресам памяти данных.
*/
ASSERT(. < _text_size, ".text section exceeds instruction memory size")
. = _data_base_addr;
/*
Следующая команда сообщает, что начиная с адреса, которому в данных момент
равен счетчик адресов (_data_base_addr) будет находиться секция .data
итогового файла, которая состоит из секций всех секций, начинающихся
на .data во всех переданных компоновщику двоичных файлах.
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "ram".
*/
.data : {*(.*data*)} >ram
/*
.data : AT (0x00800000) {
/*
Общепринято присваивать GP значение равное началу секции данных, смещенное
на 2048 байт вперед.
Благодаря относительной адресации со смещением в 12 бит, можно адресоваться
@@ -116,13 +109,16 @@ SECTIONS
уже хранит базовый адрес и нужно только смещение).
Подробнее:
https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ
*/
_gbl_ptr = _data_base_addr + 0x800;
*/
_gbl_ptr = . + 2048;
*(.*data*)
*(.sdata*)
} > data_mem
/*
Поскольку мы не знаем суммарный размер всех используемых секций данных,
перед размещением других секций, необходимо выравнять счетчик адресов по
перед размещением других секций, необходимо выровнять счетчик адресов по
4х-байтной границе.
*/
. = ALIGN(4);
@@ -151,10 +147,13 @@ SECTIONS
https://en.wikipedia.org/wiki/.bss
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "ram".
регионе "data_mem".
*/
_bss_start = .;
.bss : {*(.*bss*)} >ram
.bss : {
*(.bss*)
*(.sbss*)
} > data_mem
_bss_end = .;
@@ -166,20 +165,17 @@ SECTIONS
Поскольку стеков у нас два, в самом низу мы разместим стек прерываний, а
над ним программный стек. При этом надо обеспечить защиту программного
стека от наложения на него стека прерываний.
Однако перед этим, мы должны убедиться, что под программный стек останется
хотя бы 1280 байт (ничем не обоснованное число, взятое с потолка).
Такое значение обеспечивает до 16 вложенных вызовов (если сохранять только
необерегаемые регистры).
Однако перед этим, мы должны убедиться, что под оба стека хватит места.
=================================
*/
/* Мы хотим гарантировать, что под стек останется как минимум 1280 байт */
ASSERT(. < (_data_end - _trap_stack_size - _stack_size),
/* Мы хотим гарантировать, что под стек останется место */
ASSERT(. < (LENGTH(data_mem) - _trap_stack_size - _stack_size),
"Program size is too big")
/* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли
использовать его в вызове ALIGN) */
. = _data_end - _trap_stack_size;
. = LENGTH(data_mem) - _trap_stack_size;
/*
Размещаем указатель программного стека так близко к границе стека
@@ -188,13 +184,14 @@ SECTIONS
Подробнее:
https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
*/
_stack_ptr = ALIGN(16) <= _data_end - _trap_stack_size?
_stack_ptr = ALIGN(16) <= LENGTH(data_mem) - _trap_stack_size?
ALIGN(16) : ALIGN(16) - 16;
ASSERT(_stack_ptr <= _data_end - _trap_stack_size, "SP exceed memory size")
ASSERT(_stack_ptr <= LENGTH(data_mem) - _trap_stack_size,
"SP exceed memory size")
/* Перемещаем счетчик адресов в конец памяти (чтобы после мы могли
использовать его в вызове ALIGN) */
. = _data_end;
. = LENGTH(data_mem);
/*
Обычно память имеет размер, кратный 16, но на случай, если это не так, мы
@@ -202,6 +199,6 @@ SECTIONS
конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти,
округленного до 16 в сторону большего значения
*/
_trap_stack_ptr = ALIGN(16) <= _data_end ? ALIGN(16) : ALIGN(16) - 16;
ASSERT(_trap_stack_ptr <= _data_end, "ISP exceed memory size")
_trap_stack_ptr = ALIGN(16) <= LENGTH(data_mem) ? ALIGN(16) : ALIGN(16) - 16;
ASSERT(_trap_stack_ptr <= LENGTH(data_mem), "ISP exceed memory size")
}