mirror of
https://github.com/MPSU/APS.git
synced 2026-06-13 12:33:33 +00:00
English version draft
Assisted-by: Claude:claude-4.6-sonnet
This commit is contained in:
@@ -9,21 +9,21 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
|
||||
* ------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
|
||||
OUTPUT_FORMAT("elf32-littleriscv") /* Specify the byte order */
|
||||
|
||||
ENTRY(_start) /* мы сообщаем компоновщику, что первая
|
||||
исполняемая процессором инструкция
|
||||
находится у метки "_start"
|
||||
ENTRY(_start) /* We tell the linker that the first
|
||||
instruction executed by the processor
|
||||
is at the label "_start"
|
||||
*/
|
||||
|
||||
/*
|
||||
В данном разделе указывается структура памяти:
|
||||
Сперва идет регион "instr_mem", являющийся памятью с исполняемым кодом
|
||||
(об этом говорит аттрибут 'x'). Этот регион начинается
|
||||
с адреса 0x00000000 и занимает 1024 байта.
|
||||
Далее идет регион "data_mem", начинающийся с адреса 0x00000000 и занимающий
|
||||
2048 байт. Этот регион является памятью, противоположной региону "instr_mem"
|
||||
(в том смысле, что это не память с исполняемым кодом).
|
||||
This section specifies the memory structure:
|
||||
First comes the "instr_mem" region, which is executable memory
|
||||
(indicated by the 'x' attribute). This region starts
|
||||
at address 0x00000000 and occupies 1024 bytes.
|
||||
Next comes the "data_mem" region, starting at address 0x00000000 and
|
||||
occupying 2048 bytes. This region is non-executable memory
|
||||
(in the sense that it does not contain executable code).
|
||||
*/
|
||||
MEMORY
|
||||
{
|
||||
@@ -31,51 +31,50 @@ MEMORY
|
||||
data_mem (!x) : ORIGIN = 0x00000000, LENGTH = 2K
|
||||
}
|
||||
|
||||
_trap_stack_size = 640; /* Размер стека обработчика перехватов.
|
||||
Данный размер позволяет выполнить
|
||||
до 8 вложенных вызовов при обработке
|
||||
перехватов.
|
||||
_trap_stack_size = 640; /* Size of the trap handler stack.
|
||||
This size allows up to 8 nested
|
||||
calls during trap handling.
|
||||
*/
|
||||
|
||||
_stack_size = 640; /* Размер программного стека.
|
||||
Данный размер позволяет выполнить
|
||||
от 8 вложенных вызовов.
|
||||
_stack_size = 640; /* Size of the program stack.
|
||||
This size allows up to 8 nested
|
||||
calls.
|
||||
*/
|
||||
|
||||
/*
|
||||
В данном разделе описывается размещение программы в памяти.
|
||||
Программа разделяется на различные секции:
|
||||
- секции исполняемого кода программа;
|
||||
- секции статических переменных и массивов, значение которых должно быть
|
||||
"вшито" в программу;
|
||||
и т.п.
|
||||
This section describes the placement of the program in memory.
|
||||
The program is divided into various sections:
|
||||
- sections of the executable code;
|
||||
- sections of static variables and arrays whose values must be
|
||||
embedded in the program;
|
||||
etc.
|
||||
*/
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
|
||||
/*
|
||||
В скриптах компоновщика есть внутренняя переменная, записываемая как '.'
|
||||
Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в
|
||||
памяти.
|
||||
В начале файла она инициализируется нулем. Добавляя новые секции, эта
|
||||
переменная будет увеличиваться на размер каждой новой секции.
|
||||
Если при размещении секций не указывается никакой адрес, они будут размещены
|
||||
по текущему значению счетчика адресов.
|
||||
Этой переменной можно присваивать значения, после этого, она будет
|
||||
увеличиваться с этого значения.
|
||||
Подробнее:
|
||||
In linker scripts there is an internal variable written as '.'
|
||||
This variable is called the "location counter". It stores the current
|
||||
address in memory.
|
||||
At the beginning of the file it is initialized to zero. As new sections
|
||||
are added, this variable is incremented by the size of each new section.
|
||||
If no address is specified when placing sections, they will be placed
|
||||
at the current value of the location counter.
|
||||
This variable can be assigned values; after that, it will increment
|
||||
from that value.
|
||||
More details:
|
||||
https://home.cs.colorado.edu/~main/cs1300/doc/gnu/ld_3.html#IDX338
|
||||
*/
|
||||
|
||||
/*
|
||||
Следующая команда сообщает, что начиная с адреса, которому в данных момент
|
||||
равен счетчик адресов (в данный момент, начиная с нуля) будет находиться
|
||||
секция .text итогового файла, которая состоит из секций .boot, а также всех
|
||||
секций, начинающихся на .text во всех переданных компоновщику двоичных
|
||||
файлах.
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "instr_mem".
|
||||
The following command specifies that starting from the address currently
|
||||
held by the location counter (at this point, starting from zero), the
|
||||
.text section of the output file will be located, consisting of the .boot
|
||||
sections as well as all sections starting with .text from all binary files
|
||||
passed to the linker.
|
||||
We additionally specify that this section must be placed in the
|
||||
"instr_mem" region.
|
||||
*/
|
||||
.text : {
|
||||
PROVIDE(_start = .);
|
||||
@@ -85,29 +84,29 @@ SECTIONS
|
||||
|
||||
|
||||
/*
|
||||
Секция данных размещается аналогично секции инструкций за исключением
|
||||
адреса загрузки в памяти (Load Memory Address, LMA). Поскольку память
|
||||
инструкций и данных физически разделены, у них есть пересекающееся адресное
|
||||
пространство, которое мы бы хотели использовать (поэтому в разделе MEMORY мы
|
||||
указали что стартовые адреса обоих памятей равны нулю). Однако компоновщику
|
||||
это не нравится, ведь как он будет размещать две разные секции в одно и то же
|
||||
место. Поэтому мы ему сообщаем, с помощью оператора "AT", что загружать секцию
|
||||
данных нужно на самом деле не по нулевому адресу, а по какому-то другому,
|
||||
заведомо большему чем размер памяти инструкций, но процессор будет
|
||||
использовать адреса, начинающиеся с нуля. Такой вариант компоновщика
|
||||
устраивает и он собирает исполняемый файл без ошибок. Наша же задача,
|
||||
загрузить итоговую секцию данных по нулевым адресам памяти данных.
|
||||
The data section is placed similarly to the instruction section, except
|
||||
for the Load Memory Address (LMA). Since instruction and data memories are
|
||||
physically separate, they have overlapping address spaces that we would
|
||||
like to use (which is why in the MEMORY section we set the start addresses
|
||||
of both memories to zero). However, the linker does not like this, since
|
||||
how can it place two different sections in the same location? So we tell it,
|
||||
using the "AT" operator, that the data section should actually be loaded
|
||||
at a different address — one that is guaranteed to be larger than the size
|
||||
of the instruction memory — while the processor will use addresses starting
|
||||
from zero. The linker accepts this arrangement and builds the executable
|
||||
without errors. Our task is then to load the final data section at address
|
||||
zero in data memory.
|
||||
*/
|
||||
.data : AT (0x00800000) {
|
||||
/*
|
||||
Общепринято присваивать GP значение равное началу секции данных, смещенное
|
||||
на 2048 байт вперед.
|
||||
Благодаря относительной адресации со смещением в 12 бит, можно адресоваться
|
||||
на начало секции данных, а также по всему адресному пространству вплоть до
|
||||
4096 байт от начала секции данных, что сокращает объем требуемых для
|
||||
адресации инструкций (практически не используются операции LUI, поскольку GP
|
||||
уже хранит базовый адрес и нужно только смещение).
|
||||
Подробнее:
|
||||
It is conventional to assign GP a value equal to the start of the data
|
||||
section offset forward by 2048 bytes.
|
||||
With 12-bit relative addressing with offset, it is possible to address
|
||||
the beginning of the data section as well as the entire address space up
|
||||
to 4096 bytes from the start of the data section, which reduces the number
|
||||
of addressing instructions needed (LUI operations are rarely used since GP
|
||||
already holds the base address and only an offset is needed).
|
||||
More details:
|
||||
https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ
|
||||
*/
|
||||
_gbl_ptr = . + 2048;
|
||||
@@ -117,37 +116,38 @@ SECTIONS
|
||||
|
||||
|
||||
/*
|
||||
Поскольку мы не знаем суммарный размер всех используемых секций данных,
|
||||
перед размещением других секций, необходимо выровнять счетчик адресов по
|
||||
4х-байтной границе.
|
||||
Since we do not know the total size of all data sections used,
|
||||
before placing other sections, we must align the location counter
|
||||
to a 4-byte boundary.
|
||||
*/
|
||||
. = ALIGN(4);
|
||||
|
||||
|
||||
/*
|
||||
BSS (block started by symbol, неофициально его расшифровывают как
|
||||
better save space) — это сегмент, в котором размещаются неинициализированные
|
||||
статические переменные. В стандарте Си сказано, что такие переменные
|
||||
инициализируются нулем (или NULL для указателей). Когда вы создаете
|
||||
статический массив — он должен быть размещен в исполняемом файле.
|
||||
Без bss-секции, этот массив должен был бы занимать такой же объем
|
||||
исполняемого файла, какого объема он сам. Массив на 1000 байт занял бы
|
||||
1000 байт в секции .data.
|
||||
Благодаря секции bss, начальные значения массива не задаются, вместо этого
|
||||
здесь только записываются названия переменных и их адреса.
|
||||
Однако на этапе загрузки исполняемого файла теперь необходимо принудительно
|
||||
занулить участок памяти, занимаемый bss-секцией, поскольку статические
|
||||
переменные должны быть проинициализированы нулем.
|
||||
Таким образом, bss-секция значительным образом сокращает объем исполняемого
|
||||
файла (в случае использования неинициализированных статических массивов)
|
||||
ценой увеличения времени загрузки этого файла.
|
||||
Для того, чтобы занулить bss-секцию, в скрипте заводятся две переменные,
|
||||
указывающие на начало и конец bss-секции посредством счетчика адресов.
|
||||
Подробнее:
|
||||
BSS (block started by symbol, unofficially expanded as
|
||||
"better save space") is a segment where uninitialized static
|
||||
variables are placed. The C standard states that such variables
|
||||
are initialized to zero (or NULL for pointers). When you create
|
||||
a static array, it must be placed in the executable file.
|
||||
Without a bss section, the array would occupy as much space in the
|
||||
executable as its own size. A 1000-byte array would take 1000 bytes
|
||||
in the .data section.
|
||||
Thanks to the bss section, the initial values of the array are not stored;
|
||||
instead, only the variable names and their addresses are recorded.
|
||||
However, during executable loading, the memory region occupied by the
|
||||
bss section must be explicitly zeroed out, since static variables must
|
||||
be initialized to zero.
|
||||
Thus, the bss section significantly reduces the size of the executable
|
||||
(when using uninitialized static arrays) at the cost of increased
|
||||
loading time.
|
||||
To zero out the bss section, two variables are defined in the script
|
||||
that point to the beginning and end of the bss section via the
|
||||
location counter.
|
||||
More details:
|
||||
https://en.wikipedia.org/wiki/.bss
|
||||
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "data_mem".
|
||||
We additionally specify that this section must be placed in the
|
||||
"data_mem" region.
|
||||
*/
|
||||
_bss_start = .;
|
||||
.bss : {
|
||||
@@ -158,30 +158,32 @@ SECTIONS
|
||||
|
||||
|
||||
/*=================================
|
||||
Секция аллоцированных данных завершена, остаток свободной памяти отводится
|
||||
под программный стек, стек прерываний и (возможно) кучу. В соглашении о
|
||||
вызовах архитектуры RISC-V сказано, что стек растет снизу вверх, поэтому
|
||||
наша цель разместить его в самых последних адресах памяти.
|
||||
Поскольку стеков у нас два, в самом низу мы разместим стек прерываний, а
|
||||
над ним программный стек. При этом надо обеспечить защиту программного
|
||||
стека от наложения на него стека прерываний.
|
||||
Однако перед этим, мы должны убедиться, что под оба стека хватит места.
|
||||
The allocated data section is complete; the remaining free memory is
|
||||
reserved for the program stack, the trap stack, and (possibly) the heap.
|
||||
The RISC-V calling convention states that the stack grows downward
|
||||
(from higher to lower addresses), so our goal is to place it at the
|
||||
highest addresses in memory.
|
||||
Since we have two stacks, the trap stack is placed at the very bottom
|
||||
and the program stack above it. We must also ensure that the program
|
||||
stack is protected from being overwritten by the trap stack.
|
||||
Before doing so, however, we must verify that there is enough room
|
||||
for both stacks.
|
||||
=================================
|
||||
*/
|
||||
|
||||
/* Мы хотим гарантировать, что под стек останется место */
|
||||
/* We want to guarantee that there is room left for the stack */
|
||||
ASSERT(. < (LENGTH(data_mem) - _trap_stack_size - _stack_size),
|
||||
"Program size is too big")
|
||||
|
||||
/* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли
|
||||
использовать его в вызове ALIGN) */
|
||||
/* Move the location counter above the trap stack (so that we can
|
||||
use it in the ALIGN call later) */
|
||||
. = LENGTH(data_mem) - _trap_stack_size;
|
||||
|
||||
/*
|
||||
Размещаем указатель программного стека так близко к границе стека
|
||||
прерываний, насколько можно с учетом требования о выравнивании адреса
|
||||
стека до 16 байт.
|
||||
Подробнее:
|
||||
Place the program stack pointer as close to the trap stack boundary
|
||||
as possible, subject to the requirement that the stack address be
|
||||
aligned to 16 bytes.
|
||||
More details:
|
||||
https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
|
||||
*/
|
||||
_stack_ptr = ALIGN(16) <= LENGTH(data_mem) - _trap_stack_size?
|
||||
@@ -189,15 +191,15 @@ SECTIONS
|
||||
ASSERT(_stack_ptr <= LENGTH(data_mem) - _trap_stack_size,
|
||||
"SP exceed memory size")
|
||||
|
||||
/* Перемещаем счетчик адресов в конец памяти (чтобы после мы могли
|
||||
использовать его в вызове ALIGN) */
|
||||
/* Move the location counter to the end of memory (so that we can
|
||||
use it in the ALIGN call later) */
|
||||
. = LENGTH(data_mem);
|
||||
|
||||
/*
|
||||
Обычно память имеет размер, кратный 16, но на случай, если это не так, мы
|
||||
делаем проверку, после которой мы либо остаемся в самом конце памяти (если
|
||||
конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти,
|
||||
округленного до 16 в сторону большего значения
|
||||
Memory size is usually a multiple of 16, but in case it is not, we
|
||||
perform a check: we either stay at the very end of memory (if the
|
||||
end is a multiple of 16), or move 16 bytes up from the memory edge
|
||||
rounded up to the nearest multiple of 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")
|
||||
|
||||
Reference in New Issue
Block a user