Files
APS/Labs/14. Programming/linker_script.ld

205 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -----------------------------------------------------------------------------
* Project Name : Architectures of Processor Systems (APS) lab work
* Organization : National Research University of Electronic Technology (MIET)
* Department : Institute of Microdevices and Control Systems
* Author(s) : Andrei Solodovnikov
* Email(s) : hepoh@org.miet.ru
See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
* ------------------------------------------------------------------------------
*/
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
ENTRY(_start) /* мы сообщаем компоновщику, что первая
исполняемая процессором инструкция
находится у метки "_start"
*/
/*
В данном разделе указывается структура памяти:
Сперва идет регион "instr_mem", являющийся памятью с исполняемым кодом
(об этом говорит аттрибут 'x'). Этот регион начинается
с адреса 0x00000000 и занимает 1024 байта.
Далее идет регион "data_mem", начинающийся с адреса 0x00000000 и занимающий
2048 байт. Этот регион является памятью, противоположной региону "instr_mem"
(в том смысле, что это не память с исполняемым кодом).
*/
MEMORY
{
instr_mem (x) : ORIGIN = 0x00000000, LENGTH = 1K
data_mem (!x) : ORIGIN = 0x00000000, LENGTH = 2K
}
_trap_stack_size = 640; /* Размер стека обработчика перехватов.
Данный размер позволяет выполнить
до 8 вложенных вызовов при обработке
перехватов.
*/
_stack_size = 640; /* Размер программного стека.
Данный размер позволяет выполнить
от 8 вложенных вызовов.
*/
/*
В данном разделе описывается размещение программы в памяти.
Программа разделяется на различные секции:
- секции исполняемого кода программа;
- секции статических переменных и массивов, значение которых должно быть
"вшито" в программу;
и т.п.
*/
SECTIONS
{
/*
В скриптах компоновщика есть внутренняя переменная, записываемая как '.'
Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в
памяти.
В начале файла она инициализируется нулем. Добавляя новые секции, эта
переменная будет увеличиваться на размер каждой новой секции.
Если при размещении секций не указывается никакой адрес, они будут размещены
по текущему значению счетчика адресов.
Этой переменной можно присваивать значения, после этого, она будет
увеличиваться с этого значения.
Подробнее:
https://home.cs.colorado.edu/~main/cs1300/doc/gnu/ld_3.html#IDX338
*/
/*
Следующая команда сообщает, что начиная с адреса, которому в данных момент
равен счетчик адресов (в данный момент, начиная с нуля) будет находиться
секция .text итогового файла, которая состоит из секций .boot, а также всех
секций, начинающихся на .text во всех переданных компоновщику двоичных
файлах.
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "instr_mem".
*/
.text : {
PROVIDE(_start = .);
*(.boot)
*(.text*)
} > instr_mem
/*
Секция данных размещается аналогично секции инструкций за исключением
адреса загрузки в памяти (Load Memory Address, LMA). Поскольку память
инструкций и данных физически разделены, у них есть пересекающееся адресное
пространство, которое мы бы хотели использовать (поэтому в разделе MEMORY мы
указали что стартовые адреса обоих памятей равны нулю). Однако компоновщику
это не нравится, ведь как он будет размещать две разные секции в одно и то же
место. Поэтому мы ему сообщаем, с помощью оператора "AT", что загружать секцию
данных нужно на самом деле не по нулевому адресу, а по какому-то другому,
заведомо большему чем размер памяти инструкций, но процессор будет
использовать адреса, начинающиеся с нуля. Такой вариант компоновщика
устраивает и он собирает исполняемый файл без ошибок. Наша же задача,
загрузить итоговую секцию данных по нулевым адресам памяти данных.
*/
.data : AT (0x00800000) {
/*
Общепринято присваивать GP значение равное началу секции данных, смещенное
на 2048 байт вперед.
Благодаря относительной адресации со смещением в 12 бит, можно адресоваться
на начало секции данных, а также по всему адресному пространству вплоть до
4096 байт от начала секции данных, что сокращает объем требуемых для
адресации инструкций (практически не используются операции LUI, поскольку GP
уже хранит базовый адрес и нужно только смещение).
Подробнее:
https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ
*/
_gbl_ptr = . + 2048;
*(.*data*)
*(.sdata*)
} > data_mem
/*
Поскольку мы не знаем суммарный размер всех используемых секций данных,
перед размещением других секций, необходимо выровнять счетчик адресов по
4х-байтной границе.
*/
. = ALIGN(4);
/*
BSS (block started by symbol, неофициально его расшифровывают как
better save space) — это сегмент, в котором размещаются неинициализированные
статические переменные. В стандарте Си сказано, что такие переменные
инициализируются нулем (или NULL для указателей). Когда вы создаете
статический массив — он должен быть размещен в исполняемом файле.
Без bss-секции, этот массив должен был бы занимать такой же объем
исполняемого файла, какого объема он сам. Массив на 1000 байт занял бы
1000 байт в секции .data.
Благодаря секции bss, начальные значения массива не задаются, вместо этого
здесь только записываются названия переменных и их адреса.
Однако на этапе загрузки исполняемого файла теперь необходимо принудительно
занулить участок памяти, занимаемый bss-секцией, поскольку статические
переменные должны быть проинициализированы нулем.
Таким образом, bss-секция значительным образом сокращает объем исполняемого
файла (в случае использования неинициализированных статических массивов)
ценой увеличения времени загрузки этого файла.
Для того, чтобы занулить bss-секцию, в скрипте заводятся две переменные,
указывающие на начало и конец bss-секции посредством счетчика адресов.
Подробнее:
https://en.wikipedia.org/wiki/.bss
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "data_mem".
*/
_bss_start = .;
.bss : {
*(.bss*)
*(.sbss*)
} > data_mem
_bss_end = .;
/*=================================
Секция аллоцированных данных завершена, остаток свободной памяти отводится
под программный стек, стек прерываний и (возможно) кучу. В соглашении о
вызовах архитектуры RISC-V сказано, что стек растет снизу вверх, поэтому
наша цель разместить его в самых последних адресах памяти.
Поскольку стеков у нас два, в самом низу мы разместим стек прерываний, а
над ним программный стек. При этом надо обеспечить защиту программного
стека от наложения на него стека прерываний.
Однако перед этим, мы должны убедиться, что под оба стека хватит места.
=================================
*/
/* Мы хотим гарантировать, что под стек останется место */
ASSERT(. < (LENGTH(data_mem) - _trap_stack_size - _stack_size),
"Program size is too big")
/* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли
использовать его в вызове ALIGN) */
. = LENGTH(data_mem) - _trap_stack_size;
/*
Размещаем указатель программного стека так близко к границе стека
прерываний, насколько можно с учетом требования о выравнивании адреса
стека до 16 байт.
Подробнее:
https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
*/
_stack_ptr = ALIGN(16) <= LENGTH(data_mem) - _trap_stack_size?
ALIGN(16) : ALIGN(16) - 16;
ASSERT(_stack_ptr <= LENGTH(data_mem) - _trap_stack_size,
"SP exceed memory size")
/* Перемещаем счетчик адресов в конец памяти (чтобы после мы могли
использовать его в вызове ALIGN) */
. = LENGTH(data_mem);
/*
Обычно память имеет размер, кратный 16, но на случай, если это не так, мы
делаем проверку, после которой мы либо остаемся в самом конце памяти (если
конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти,
округленного до 16 в сторону большего значения
*/
_trap_stack_ptr = ALIGN(16) <= LENGTH(data_mem) ? ALIGN(16) : ALIGN(16) - 16;
ASSERT(_trap_stack_ptr <= LENGTH(data_mem), "ISP exceed memory size")
}