ld.lld and linker files
LLVM’s linker was a real pain…
LLVM linker (ld.lld-5.0)
To get my STM32 to run I have to tell the linker where to store the executable data.
This is usually done with a linker file. But unfortunately ld.lld
doesn’t support the same gcc-linker syntax.
Memory definition
We first start with the memory definition file
STM32-linker/STM32F3/STM32F303VC.ld
MEMORY{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 40K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 8K
}
The STM32F303VC6 which is used in the STM32F3Discovery board is defined by this (mem) linker file.
We see that this Chip has a RAM
region in 0x20000000
which is 40KB long and used for saving variables and the stack.
Also it has a FLASH
region for the program code at the adress 0x0800000
which is 256KB long.
And we just ignore the CCMRAM
.
(simple) linker file
Now we want to define a simple linker file. We tell meson in the meson.build
file to use the memory layout file and append the simple linker file together.
So we have a uC agnostic linker file and for every uC an independent memory layout file.
OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
/* define entry point */
ENTRY(Reset_Handler)
first we have some configuration shenanigans. Without any further explanation.
Then starts the black magic
/* calculate the Last RAM address*/
_estack = ORIGIN(RAM) + LENGTH(RAM) - 1;
we first define a variable with the the start-address of the stack pointer. It will be on the top of the RAM memory.
SECTIONS
{
/*--------------------------------------------------------------------------*/
/* interrupt vector goes at the beginning of FLASH */
.isr_vector : {
. = ALIGN(4); /* align */
_sisr_vector = .; /* define start symbol */
KEEP(*(.isr_vector)) /* Interrupt vectors */
. = ALIGN(4); /* align */
_eisr_vector = .; /* define end symbol */
} > FLASH
then we define the first memory entry in the FLASH memory. It’s the interrupt vector table.
define a section
this syntax defines a block with the name .<region>
which going to be saved in the FLASH
memory
.<region> : {
...
} > FLASH
The command KEEP(*(.isr_vector))
will copy the section defined in the startupfile with the name .isr_vector
in the surrounding <region>
.
Sadly I used the same name for region and the section defined in the startupfile.
next we copy the program code and constant values to the FLASH
memory
/*--------------------------------------------------------------------------*/
/* program data goes into FLASH */
.text : {
. = ALIGN(4); /* align */
_stext = .; /* define start symbol */
*(.text) /* insert program code .text */
*(.text*) /* .text* sections */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
. = ALIGN(4); /* align */
_etext = .; /* define end symbol */
} > FLASH
/*--------------------------------------------------------------------------*/
/* constant data goes into FLASH */
.rodata : {
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4); /* align */
_erodata = .;
} > FLASH
As you can read in the comments : *(.text)
is the program code and the glue sections are some special arm / thumb magic.
then follows some more arm magic
/* ARM stack unwinding section (GDB uses this) */
.ARM.extab : {
__extab_start__ = .;/* define start symbol */
*(.ARM.extab* .gnu.linkonce.armextab.*)
__extab_end__ = .; /* define end symbol */
} > FLASH
.ARM : {
__exidx_start__ = .;/* define start symbol */
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
__exidx_end__ = .; /* define end symbol */
} > FLASH
Initialized globals
Then we want to save the global variables which are initialized with a defined value.
We have to link the variable addresses in the RAM memory, but we have to save the initial values in the FLASH
memory.
In gcc you would do this like this:
.<region> : {
...
} >RAM AT >FLASH
But this failes in llvm’s linker.
I had some time to figure this out…
- define all the
FLASH
regions - set the current address locator to the RAM address space
. = ORIGIN(RAM);
And then define the RAM region which values are stored in FLASH memory
. = ORIGIN(RAM);
.<region> : AT(<Flash-location>) {
...
} > RAM
<marker> = LOADADRESS(.<region>)
Any other way will fail!
So in the end I defined it that way:
. = ORIGIN(RAM);
.data : AT(__exidx_end__) {
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4); /* align */
_edata = .; /* define a global symbol at data end */
} > RAM
_sidata = LOADADDR(.data); /* get the start address of the .data section */
As you might expect *(.data)
are the values. We store them appending to the __exidx_end__
address in FLASH
.
The linker will reserve the same space in RAM
. Also we define a location variable _sidata
and copy the address of the .data
section to it
We will need to copy the values from FLASH
to RAM
by Hand in the startup file.
and then follows the so called .bss
section with uninitialized variables and a special arm attribute .ARM.attributes 0 : { *(.ARM.attributes) }
conclusion
After hours of trial and error I finally got a minimalistic linker file to work with my microcontrollers.
ld.lld
might not be in a very mature state at the moment.
But maybe this will be better with LLVM-6.0