Created
September 16, 2022 18:31
-
-
Save den-mentiei/5e14f054b670885e6ac47dd6f3273090 to your computer and use it in GitHub Desktop.
The Most Thoroughly Commented Linker Script in The World (c) theacodes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
The Most Thoroughly Commented Linker Script in The World | |
This is a linker script for the Atmel/Microchip SAM D21 | |
with an absolutely obscene amount of documentation. | |
*/ | |
/* | |
OUTPUT_FORMAT configures the linker to use a platform-specific BFD | |
backend to generate ELF files. BFD backends instruct the linker on | |
how to properly create the ELF sections for a given platform. | |
The first is the default BFD. The second and third arguments are used | |
when big (-EB) or little (-EL) endian is requested. | |
Since the SAM D series are configured with only little endian support, | |
"elf32-littlearm" is used across the board. This option seems to be | |
included by Atmel/Microchip out of an abundance of caution, as | |
arm-none-eabi-ld will do the right thing and use "elf32-littlearm" by | |
default. | |
The list of acceptable values can be obtained using `objdump -i`. | |
References: | |
* https://sourceware.org/binutils/docs/ld/Format-Commands.html#Format-Commands | |
* https://sourceware.org/binutils/docs/ld/BFD.html | |
* https://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D21_DA1_Family_DataSheet_DS40001882F.pdf | |
Section 11.1.11, Cortex M0+ Configuration | |
*/ | |
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") | |
/* | |
CPU memory configuration variables. | |
These variables are used by the following "MEMORY" command to define | |
the various memory spaces. | |
For the SAMD21G18A used by this project, the available Flash is | |
256kB and the available SRAM is 32kB. | |
This project also reserves 8kB for the bootloader and 1kB for | |
"non-volatile memory" (NVM) - which is used by the application | |
to store calibration and user settings. | |
Also it's useful to note that you can actually use unit suffixes | |
here: I could have written `FLASH_SIZE = 256KB` instead of | |
`FLASH_SIZE = 0x40000`. However, I generally prefer the hex | |
representation because there's less ambiguity. | |
References: | |
* https://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D21_DA1_Family_DataSheet_DS40001882F.pdf | |
Section 10.2, Physical Memory Map | |
*/ | |
FLASH_SIZE = 0x40000; /* 256kB, 262,144 bytes */ | |
BOOTLOADER_SIZE = 0x2000; /* 8kB, 8,192 bytes */ | |
NVM_SIZE = 0x400; /* 1kB, 1,024 bytes */ | |
SRAM_SIZE = 0x8000; /* 32kB, 32,768 bytes */ | |
/* | |
ARM Cortex-M processors use a descending stack and generally | |
require stack space to be set aside in RAM. | |
The application's behavior determines just how much stack space | |
should be reserved. I generally start with 2kB (0x800) of | |
stack space for Cortex-M0+ projects programmed in C . | |
You can analyze stack usage in GCC using the `-fstack-usage` | |
flag and you can enable compiler warnings for stack usage | |
with `-Wstack-usage=STACK_SIZE`. | |
References: | |
* https://embeddedartistry.com/blog/2020/08/17/three-gcc-flags-for-analyzing-memory-usage/ | |
* https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/how-much-stack-memory-do-i-need-for-my-arm-cortex--m-applications | |
* https://gcc.gnu.org/onlinedocs/gnat_ugn/Static-Stack-Usage-Analysis.html | |
* https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html | |
*/ | |
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x800; | |
/* | |
Memory space definition. | |
This section declare blocks of memories for specific purposes. Since an | |
ARM's address space is generally split between Flash, SRAM, peripherals, | |
and other regions, it's necessary to tell the linker where different | |
types of data can go in the address space. | |
These blocks will be used in the "SECTIONS" command below. | |
References: | |
* https://sourceware.org/binutils/docs/ld/MEMORY.html#MEMORY | |
* https://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D21_DA1_Family_DataSheet_DS40001882F.pdf | |
Section 10.2, Physical Memory Map | |
*/ | |
MEMORY | |
{ | |
/* | |
Start with the Flash memory region. On the SAMD21, Flash starts at | |
the beginning of the address space (0x00000000) and is contiguous | |
right up to the size of the Flash. Flash is marked a "rx" so | |
that the linker knows that this space is read-only (r) and | |
executable (x). | |
*/ | |
/* | |
The "bootloader" section allows this firmware to work with the uf2 | |
bootloader. The bootloader takes the first 0x2000 bytes of flash | |
memory. | |
References: | |
* https://github.com/adafruit/uf2-samdx1#configuration | |
*/ | |
bootloader (rx) : ORIGIN = 0x00000000, LENGTH = BOOTLOADER_SIZE | |
/* | |
Following the bootloader is the flash memory used by the application, | |
called "rom" here - even though it's flash, the name is just a name | |
and doesn't carry special meaning. | |
If your application isn't using a bootloader then this section | |
would start at 0x00000000, but, since this project does use | |
a bootloader it instead starts right after the end of the bootloader. | |
The total length of the rom block is the MCU's flash size minus the | |
bootloader's size and any space reserved for "non-volatile memory" | |
by the application. | |
*/ | |
rom (rx) : ORIGIN = BOOTLOADER_SIZE, LENGTH = FLASH_SIZE - BOOTLOADER_SIZE - NVM_SIZE | |
/* | |
The "nvm" block is space set aside for the application to store | |
user settings and calibration data in the MCU's flash. | |
The block is located right at the end of the flash space. This | |
is useful because it means that it stays in a fixed location | |
regardless of how much flash space the application takes up | |
in "rom". Explicitly defining this section also lets the | |
linker ensure that application code doesn't overwrite the | |
data in this region. | |
This block is marked as read-only (r) because flash can not | |
be written in the same way as normal memory, however, the | |
application can use the SAMD's NVM peripheral to write data in | |
this region. | |
*/ | |
nvm (r) : ORIGIN = FLASH_SIZE - NVM_SIZE, LENGTH = NVM_SIZE | |
/* | |
The "ram" block is mapped to the CPU's SRAM and it's where | |
the stack, heap, and all variables will go. | |
For the SAMD21, SRAM starts at 0x20000000 and is contiguous | |
for the size of the SRAM. This is noted in the Physical | |
Memory Map section of the datasheet. | |
*/ | |
ram (rwx) : ORIGIN = 0x20000000, LENGTH = SRAM_SIZE | |
} | |
/* | |
The sections command tells the linker how to combine the | |
input files into an output ELF and where segments belong | |
in memory. | |
The linker takes a set of input files containing the "input | |
sections" and uses this to map them to "output sections" | |
which are placed in the output ELF file. | |
While the most important sections to think about here | |
are the ones that'll be placed into the memory (segments) | |
some sections are just placed in the output ELF for debugging. | |
References: | |
* https://sourceware.org/binutils/docs/ld/SECTIONS.html#SECTIONS | |
*/ | |
SECTIONS | |
{ | |
/* | |
The text segment contains program code and read-only data. | |
References: | |
* https://developer.arm.com/documentation/dui0101/a/ | |
Page 5, Segments | |
* http://www.sco.com/developers/gabi/latest/ch4.sheader.html#special_sections | |
*/ | |
.text : | |
{ | |
/* | |
This segment must be 4-byte aligned as defined in ARM ELF | |
File Format specification. | |
*/ | |
. = ALIGN(4); | |
/* | |
The vector table defines the initial stack pointer and | |
interrupt/exception routines for the ARM CPU and device | |
peripherals. Every Cortex-M project needs this. | |
For the SAM D series the vector table is expected | |
to be at address 0x00000000 after reset. Since | |
flash memory starts at 0x00000000, the first values | |
in flash should be the vector table. | |
When defining the vector table in code you must use | |
`__attribute__ ((section(".vectors")))` to tell | |
GCC to place the vector table into the section | |
named ".vectors" in the input object file so that | |
the linker can find it. | |
Also notice the use of `KEEP` here. To save on size, | |
the firmware is compiled with options that let GCC | |
discard unused functions and data (`--gc-sections`). | |
Without `KEEP`, the linker would throw away the vector | |
table! | |
Note that since this project uses the UF2 bootloader, | |
this actually gets placed at the beginning of the | |
program's flash area (0x2000). The Cortex-M allows | |
changing the vector table after initialization, | |
so the startup script sets the Vector Table Offset | |
Register (`SCB->VTOR`) to `_sfixed` during its | |
intialization. The `_efixed` symbol is unused but | |
included for completeness. | |
References: | |
* https://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D21_DA1_Family_DataSheet_DS40001882F.pdf | |
Secion 8.3.3, Fetching of Initial Instructions | |
* https://static.docs.arm.com/ddi0403/eb/DDI0403E_B_armv7m_arm.pdf | |
Section B1.5.3, The vector table | |
Section B3.2.5, Vector Table Offset Register, VTOR | |
* ../third_party/samd21/gcc/gcc/startup_samd21.c | |
* https://gcc.gnu.org/onlinedocs/gnat_ugn/Compilation-options.html | |
*/ | |
_sfixed = .; | |
KEEP(*(.vectors .vectors.*)) | |
/* | |
Include code and read-only data sections from all | |
input files. | |
By default, GCC places all program code into a section named | |
".text" and read-only data (such as const static variables) into | |
a section named ".rodata" in the input object files. This naming | |
convention is from the ELF ABI specification. | |
GCC generates three "flavors" of sections in object files: | |
- .{section}: the basic section. | |
- .{section}.*: sections generated by "-ffunction-sections" and | |
"-fdata-sections" so that each function/data has a unique | |
section. | |
- .gnu.linkonce.{type}.*: sections generated by GCC so the | |
linker can remove duplicates. Seems to be related to | |
Vague Linking. | |
References: | |
* http://www.sco.com/developers/gabi/latest/ch4.sheader.html#special_sections | |
* https://gcc.gnu.org/onlinedocs/gcc/Vague-Linkage.html | |
* https://stackoverflow.com/questions/5518083/what-is-a-linkonce-section | |
*/ | |
*(.text .text.* .gnu.linkonce.t.*) | |
*(.rodata .rodata* .gnu.linkonce.r.*) | |
/* | |
The following sections support the C & C++ runtime. | |
These are generally used by crt0. | |
References: | |
* https://en.wikipedia.org/wiki/Crt0 | |
*/ | |
/* | |
C++ Runtime: initializers for static variables. | |
C Runtime: designated constructors | |
For C++, handles variables at file scope like this: | |
int f = some_func() | |
For C, handles functions designated as constructors: | |
void intialize_thing(void) __attribute__((constructor)); | |
Executed by the C runtime at startup via __libc_init_array. | |
References: | |
* https://github.com/gcc-mirror/gcc/blob/master/libgcc/crtstuff.c | |
* https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/misc/init.c; | |
* https://gcc.gnu.org/onlinedocs/gccint/Initialization.html | |
* https://developer.arm.com/documentation/dui0475/h/the-arm-c-and-c---libraries/c---initialization--construction-and-destruction | |
* https://stackoverflow.com/questions/15265295/understanding-the-libc-init-array | |
*/ | |
. = ALIGN(4); | |
KEEP(*(.init)) | |
. = ALIGN(4); | |
__preinit_array_start = .; | |
KEEP (*(.preinit_array)) | |
__preinit_array_end = .; | |
. = ALIGN(4); | |
__init_array_start = .; | |
KEEP (*(SORT(.init_array.*))) | |
KEEP (*(.init_array)) | |
__init_array_end = .; | |
/* | |
C++ runtime: destructors for static variables. | |
C runtime: designated finializers | |
For C, handles functions designated as destructors: | |
void destroy_thing(void) __attribute__((destructor)); | |
References: | |
* https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/misc/fini.c | |
*/ | |
. = ALIGN(4); | |
KEEP(*(.fini)) | |
. = ALIGN(4); | |
__fini_array_start = .; | |
KEEP (*(.fini_array)) | |
KEEP (*(SORT(.fini_array.*))) | |
__fini_array_end = .; | |
/* | |
C++ runtime: static constructors | |
References: | |
* https://gcc.gnu.org/onlinedocs/gccint/Initialization.html | |
* https://github.com/gcc-mirror/gcc/blob/master/libgcc/crtstuff.c | |
*/ | |
. = ALIGN(4); | |
KEEP (*crtbegin.o(.ctors)) | |
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) | |
KEEP (*(SORT(.ctors.*))) | |
KEEP (*crtend.o(.ctors)) | |
/* | |
C++ runtime: static destructors and atexit() | |
Note that in usual practice these aren't ever called because the program | |
doesn't exit - except when powered off or reset. | |
References: | |
* https://gcc.gnu.org/onlinedocs/gccint/Initialization.html | |
* https://github.com/gcc-mirror/gcc/blob/master/libgcc/crtstuff.c | |
*/ | |
. = ALIGN(4); | |
KEEP (*crtbegin.o(.dtors)) | |
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) | |
KEEP (*(SORT(.dtors.*))) | |
KEEP (*crtend.o(.dtors)) | |
. = ALIGN(4); | |
_efixed = .; | |
} > rom | |
/* | |
ARM defines several special sections for exception handling. | |
These are require for C++ and for C programs that try to | |
examine backtraces. | |
- exidx is used to contain index entries for stack unwinding. | |
- extab names sections containing exception unwinding information. | |
Essentially, each function that can throw an exception will | |
have entries in the exidx and extab sections. | |
References: | |
- https://developer.arm.com/documentation/ihi0038/b/ | |
- https://stackoverflow.com/a/57463515 | |
*/ | |
.ARM.extab : { | |
*(.ARM.extab* .gnu.linkonce.armextab.*) | |
} > rom | |
.ARM.exidx : { | |
PROVIDE(__exidx_start = .); | |
*(.ARM.exidx* .gnu.linkonce.armexidx.*) | |
PROVIDE(__exidx_end = .); | |
} > rom | |
/* | |
The `.relocate` section includes mutable variables that have a | |
default value and specially marked functions that should execute | |
from RAM. | |
This data is stored in ROM but is referenced from RAM. The | |
program/runtime must copy the data from ROM to RAM on reset, | |
hence, "relocate". | |
Performance sensitive/critical functions can also be placed in | |
RAM using this section: | |
#define RAMFUNC __attribute__((section(".ramfunc"))) | |
void fast_function(void) RAMFUNC; | |
In other linker scripts you might see this named as the `.data` | |
section. That's what the ELF specification calls for, but the | |
Microchip-provided SAM D startup scripts expect `.relocate`. | |
This also sets the symbol `_etext` to the start of the relocation | |
segment in flash. The startup script copies the data starting at | |
`_etext` to `_srelocate` and ends when it reaches `_erelocate`. | |
The `_etext` name is a bit unfortunate since it's not the end of | |
the text segment, but rather the start of the read-only copy of the | |
relocate section in flash. If I wrote the startup script I would have | |
named these symbols differently. | |
References: | |
* http://www.sco.com/developers/gabi/latest/ch4.sheader.html#special_sections | |
* https://www.sourceware.org/binutils/docs/ld/Output-Section-LMA.html#Output-Section-LMA | |
* ../third_party/samd21/gcc/gcc/startup_samd21.c | |
*/ | |
.relocate : | |
{ | |
. = ALIGN(4); | |
_srelocate = .; | |
*(.ramfunc .ramfunc.*); | |
*(.data .data.*); | |
. = ALIGN(4); | |
_erelocate = .; | |
} > ram AT> rom | |
_etext = LOADADDR(.relocate); | |
/* | |
The BSS section reserves RAM space for declared but uninitialized | |
variables. | |
This is zeroed out by the startup script. The start-up script | |
zeros out the area of RAM starting at `_szero` and ending at | |
`_ezero`. | |
This includes `COMMON` which is a bit of a legacy section. GCC | |
defaults to `-fno-common` these days so there shouldn't be | |
anything in there, but it's included for completeness. | |
References: | |
* http://www.sco.com/developers/gabi/latest/ch4.sheader.html#special_sections | |
* https://en.wikipedia.org/wiki/.bss | |
* https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html | |
Section -fcommon | |
* ../third_party/samd21/gcc/gcc/startup_samd21.c | |
*/ | |
.bss (NOLOAD) : | |
{ | |
. = ALIGN(4); | |
_szero = .; | |
*(.bss .bss.*) | |
*(COMMON) | |
. = ALIGN(4); | |
_ezero = .; | |
} > ram | |
/* | |
Reserve the stack space in RAM. | |
Cortex-M stacks grow down, so the stack starts at `_estack` and | |
grows towards `_sstack`. The startup script sets the vector | |
table's stack pointer to `_estack` on startup. `_sstack` is | |
unused but included for completeness. | |
The ARM procedure call standard (AAPCS) requires the stack to be | |
aligned on an eight byte boundary. | |
References: | |
* ../third_party/samd21/gcc/gcc/startup_samd21.c | |
* https://developer.arm.com/documentation/ihi0042/e/ | |
Section 5.2.1.2, Stack constraints at a public interface | |
*/ | |
.stack (NOLOAD): | |
{ | |
. = ALIGN(8); | |
_sstack = .; | |
. = . + STACK_SIZE; | |
. = ALIGN(8); | |
_estack = .; | |
} > ram | |
/* | |
Mark the end of the RAM and the start of unallocated space. | |
If the program uses the malloc and the heap, then `_heap_start` | |
can be used as the start of the heap. If the program doesn't | |
use the heap then the `_heap_start` symbol is unused and could | |
be removed. With `-specs=nano.specs`, the `_sbrk` syscall has | |
to be implemented for malloc to work: | |
extern int _heap_start; | |
void *_sbrk(int incr) { | |
static unsigned char *heap = NULL; | |
unsigned char *prev_heap; | |
if (heap == NULL) { | |
heap = (unsigned char *)&_heap_start; | |
} | |
prev_heap = heap; | |
heap += incr; | |
return prev_heap; | |
} | |
Another memory layout strategy is to place the stack at the end | |
of RAM and the heap after bss. That way the heap can grow upwards | |
towards the stack and the stack can grow downwards to the heap. | |
However, I'm not a big fan of that approach- it's possible for the | |
heap to overwrite the stack. Leaving the entire end of RAM available | |
as the heap works well for my purposes. | |
References: | |
* https://en.wikipedia.org/wiki/Sbrk | |
* https://interrupt.memfault.com/blog/boostrapping-libc-with-newlib | |
* https://embeddedartistry.com/blog/2017/02/15/implementing-malloc-first-fit-free-list/ | |
*/ | |
. = ALIGN(4); | |
PROVIDE(_heap_start = .); | |
_end = . ; | |
} | |
/* | |
Absolute symbol definitions. | |
This section defines some useful absolute symbols for the application | |
to use. | |
*/ | |
/* | |
Symbols for the settings section in the NVM memory block. | |
Gemini uses the first half of the NVM block for user settings. | |
This symbol is used by the settings module to know where to | |
load and save settings. | |
References: | |
* ../src/gem_settings_load_save.c | |
*/ | |
_nvm_settings_base_address = ORIGIN(nvm); | |
_nvm_settings_length = LENGTH(nvm) / 2; | |
/* | |
Symbols for the calibration/look-up table in the NVM memory block. | |
Gemini uses the other half of the NVM block to store the factory- | |
calibrated look-up table for translating ADC -> frequency/DAC codes. | |
References: | |
* ../src/gem_ramp_table_load_save.c | |
*/ | |
_nvm_lut_base_address = ORIGIN(nvm) + LENGTH(nvm) / 2; | |
_nvm_lut_length = LENGTH(nvm) / 2; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment