At this point he have played with the different parts, but how do code fit together? How does the system know where to put the different things?
If we look back at the first mini example, there was only 3 files in that project.
And here we will focus on the interaction between the code and the linker, in this case main.c and stm32.ld.
Before I mentioned that there needs to be a vector with data at address 0x0, and the rest just ended up in some good place and worked good enough.
If we look at the c style version of this vector (from the first mini example), it looked like this.
#define STACK_TOP 0x20000800 // Define the vector table unsigned int * myvectors[4] __attribute__ ((section("vectors")))= { (unsigned int *) STACK_TOP, // stack pointer (unsigned int *) main, // code entry point (unsigned int *) nmi_handler, // NMI handler (not really) (unsigned int *) hardfault_handler // hard fault handler (let's hope not) };
Now there is two small problems with this code. The first is that the name does not tell us anything about what is does, and the second is that we have a define that tells us where the top of the stack is. Therefore let's rename the vector to "the_nvic_vector" and call the section .nvic_vector.
unsigned int * the_nvic_vector[4] __attribute__ ((section(".nvic_vector")))= {
In the linker file we then need to add this new section called .nvic_vector and make sure that this is located at address 0x0.
MEMORY { rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K } SECTIONS { .nvic_vector : { *(vectors) /* Vector table */ } >rom }
But how did I know what to put into the linker file? (I will return to this important question shortly).
Then we can replace the define with a variable that we get from the linker. Since the linker file has the rest of this information it is a more logical place and will give us a better overview of the memory map.
extern unsigned int _STACKTOP;
And then we put the address of that variable right into the vector and we have something that looks like this. Please note that we don't care what is in this variable, only where it is located.
extern unsigned int _STACKTOP; unsigned int * the_nvic_vector[4] __attribute__ ((section(".nvic_vector")))= { (unsigned int *) &_STACKTOP, // stack pointer (unsigned int *) main, // code entry point (unsigned int *) nmi_handler, // NMI handler (not really) (unsigned int *) hardfault_handler // hard fault handler (let's hope not) };
So we start to tell what is the maximum address on this device, and we call this address stack.
MEMORY { stack(rwx): ORIGIN = 0x20004FFC, LENGTH = 0K }
Then we add section that just adds the variable _STACKTOP at this address.
SECTIONS { .stack : { _STACKTOP = .; } >stack }
And together with the nvic part we have something that looks like this.
MEMORY { stack(rwx): ORIGIN = 0x20004FFC, LENGTH = 0K rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K } SECTIONS { .nvic_vector : { *(vectors) /* Vector table */ } >rom .stack : { _STACKTOP = .; } >stack }
But how did I know this?
There is a couple of nice command that comes with binutils that can help us here.
But since we must use the platform specific version the commands, their correct names is actually arm-none-eabi-objdump and arm-none-eabi-nm. However since I will simply call them objdump and nm.
The basic idea is that the compiler will output a object file per c file, and the linker will put those object files together into 1 file that we can put on our device (kind off). objdump can be used to get information out from those objects/elf files.
If we look into the object file where we should find the nvic vector, main.o, we can use objdump to get the information that is related to the nvic vector.
$> arm-none-eabi-objdump --syms main.o | grep .nvic_vector 00000000 l d .nvic_vector 00000000 .nvic_vector 00000000 g O .nvic_vector 00000010 the_nvic_vector
On the second line we can see that our vector "the_nvic_vector", will be put into section .nvic_vector. This is actually the input data that tells the linker how to handle the code.
And if we then look at the same but after the linker, in the main.elf. This is almost the same in this case.
$> arm-none-eabi-objdump --syms main.elf | grep .nvic_vector 00000000 l d .nvic_vector 00000000 .nvic_vector 00000000 g O .nvic_vector 00000010 the_nvic_vector
But we also need to look with nm on main.elf so we can see what is at address 0, and lucky us we find our vector.
$> arm-none-eabi-nm -n main.elf | grep 00000000 00000000 D the_nvic_vector
If we do the same but with the _STACKTOP we see a different story.
$> arm-none-eabi-objdump --syms main.o | grep STACK 00000000 *UND* 00000000 _STACKTOP
Here you can see that he has no idea what to do with this strange variable, but if we then look after the linker.
$> arm-none-eabi-objdump --syms main.elf | grep STACK 20004ffc g .debug_abbrev 00000000 _STACKTOP
We notice that _STACKTOP goes into section .debug_abbrev and we notice the number 20004ffc. In stm32.ld we told him to put this variable at address 20004ffc, so that number should be a address, and we can double check that with nm.
$> arm-none-eabi-nm main.elf | grep STACK 20004ffc N _STACKTOP
But what about main, where does the actual code go?
$> arm-none-eabi-objdump --syms main.o | grep main 00000000 l df *ABS* 00000000 main.c 0000001c g F .text 0000006c main
Here we find that he will put main i a section called .text, and since we know that we need to put the code after the nvic vector we add that section to the linker file.
MEMORY { stack(rwx): ORIGIN = 0x20004FFC, LENGTH = 0K rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K } SECTIONS { .nvic_vector : { *(vectors) /* Vector table */ } >rom .text : { *(.text) /* Program code */ } >rom .stack : { _STACKTOP = .; } >stack }
And the result is that main will be put at address 0x2c.
$> arm-none-eabi-objdump --syms main.elf | grep main main.elf: file format elf32-littlearm 00000000 l df *ABS* 00000000 main.c 0000002c g F .text 0000006c main $> arm-none-eabi-nm main.elf | grep main 0000002c T main
And if we look at the relevant output from nm and sort by number, we can see that the system looks like we like it to.
$> arm-none-eabi-nm -n main.elf 00000000 D the_nvic_vector 0000002c T main 00000098 T nmi_handler 000000a4 T hardfault_handler 20004ffc N _STACKTOP
One thing that is important is the order you put things into the linker file, let's say that we put the .text section before the .nvic_vector.
SECTIONS { .text : { *(.text) /* Program code */ } >rom .nvic_vector : { *(vectors) /* Vector table */ } >rom }
Then when we look at the resulting memory map we will see thing that makes no sense.
$> arm-none-eabi-nm -n main.elf 00000000 T main 00000088 T nmi_handler 00000094 T hardfault_handler 000000e8 D the_nvic_vector
Notice how main ends up at address 0x0, and the_nvic_vector at 0xe8. With this setup the system would not even start.
Best to put them in the "correct" order.
Now we have the start vector and code in place, but what happens when I start to code? Where does my local variables go?
Short answer is that they go on the stack, and we told the system that the stack begins at the end of the ram. Then we know that on this platform the stack grows down in the address space.
Let's add a local variable and check with gdb where it is, and let's use the classic i.
int main(void) { ... int i=0; .... }
Then we flash and start the system and have a look with gdb at the address of i.
(gdb) print &i $1 = (int *) 0x20004ff0
And that is just a little bit lower than _STACKTOP that was 0x20004FFC, so I would say that this must be on the stack.
Let's add a global variable and have it init to zero, and see what happens.
unsigned int my_global_variable_zero = 0; int main(void) { ... }
Then we build and have a look at main.o.
$> arm-none-eabi-objdump --syms main.o | grep my_global_variable 00000000 g O .bss 00000004 my_global_variable_zero
So this variable goes into a section called .bss, and if you read the gcc manual you will find out that .bss is a section that variables that has a default init to zero will be put into.
So let's add a .bss section to the linker file. And have him fill from the lower address that is 0x20000000 on this chip.
MEMORY { ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K } SECTIONS { .bss : { *(.bss) /* Zero-filled run time allocate data memory */ } >ram }
Then we build again and check in the main.elf.
$ arm-none-eabi-objdump --syms main.elf | grep my_global_variable 20000000 g O .bss 00000004 my_global_variable_zero
This looks ok, but when you run this code you will notice that there is a little problem. The variable exists, but it does not default to 0! There is always something else there.
Back to school again, the point with .bss is that we can save space by not putting a lot of zeros into the flashed image. And on a bare metal system that we are building we are responsible to do that our self...!
That was not nice, let's try to set it to 1 instead. And see if that works any better.
unsigned int my_global_variable_one = 1; int main(void) { ... }
$> arm-none-eabi-objdump --syms main.o | grep my_global_variable 00000000 g O .data 00000004 my_global_variable_one
Then the global variable is put into a section called .data! so let's add a .data section.
.data : { *(.data) /* Data memory */ } >ram
But it still does not work!
Back to school again, since you can't store variables in ram over a power cycle the variables init values need to be store in a rom/flash and copied in place when the system starts.
So we need to look a a magic syntax for load address, AT. And if we add >ram AT >rom, the linker will reserve space in ram for the variable but put the init value in rom.
.data : { *(.data) /* Data memory */ } >ram AT >rom
And now the system starts, and if we look at the .data section in ram it looks ok.
$> arm-none-eabi-objdump --syms main.elf | grep my_global_variable 20000000 g O .data 00000004 my_global_variable_one
BUT how do we solve that init problem for .bss and .data?
What does the gcc ld manual tell us?
http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_21.html
And that tells us to find the start and stop address for .bss, .data and where the init data goes. And I will try to do this a little bit more clear than the manual.
For .bss it is easy..bss : { _BSS_BEGIN = .; *(.bss) /* Zero-filled run time allocate data memory */ _BSS_END = .; } >ram
Then in code we put some externs and then play with the address from those. With .bss we go over the addresses and init those to 0.
extern unsigned int _BSS_BEGIN; extern unsigned int _BSS_END; int main(void) { uint32_t* bss_begin = &_BSS_BEGIN; uint32_t* bss_end = &_BSS_END; while(bss_begin < bss_end) { *bss_begin = 0; bss_begin++; } }
The .data case is a little bit trickier, since we have two areas. The address in ram is the same style as .bss.
.data : { _DATA_BEGIN = .; *(.data) /* Data memory */ _DATA_END = .; } >ram AT >rom
But where did the init data go? The manual tricks us into think it has something to do with .text, but it has nothing to do with the .text section.
We know that AT >rom will add it to rom, so that would be after the .text section, just as .text comes right after .nvic_vector.
.text : { *(.text) /* Program code */ _DATAI_BEGIN = .; } >rom .data : { _DATA_BEGIN = .; *(.data) /* Data memory */ *(.data.*) _DATA_END = .; } >ram AT >rom .data_init : { _DATAI_END = .; } >rom
This more or less tells the linker to first save the address where .text ends in a var called _DATAI_BEGIN. Then add the init data from .data to rom, and then we have a last section that saves the address after into a var called _DATAI_END.
So we do end up with more code for this case, and then we copy from rom/flash onto the ram. And it looks like this.
extern unsigned int _DATA_BEGIN; extern unsigned int _DATA_END; extern unsigned int _DATAI_BEGIN; extern unsigned int _DATAI_END; int main(void) { uint32_t* data_begin = &_DATA_BEGIN; uint32_t* data_end = &_DATA_END; uint32_t* datai_begin = &_DATAI_BEGIN; uint32_t* datai_end = &_DATAI_END; uint32_t data_size = data_end - data_begin; uint32_t datai_size = datai_end - datai_begin; if(data_size != datai_size) { //Linker script is not correct. while(1); } while(data_begin < data_end) { *data_begin = *datai_begin; data_begin++; datai_begin++; } }
To play it safe and to check that we don't have a size mismatch between the data and the init data, I added a little size check there in the start.
What happened to my clean main function? Why is it filled with this init stuff?
Well we know why it is there, but to be honest it should not be there. This kind of stuff should be in a place called low_level_init.
So let's create it and move all of this stuff out from main.c and into a file called low_level_init.c. Then the files will look like this:
Filename: stm32.ld
MEMORY { ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K stack(rwx): ORIGIN = 0x20004FFC, LENGTH = 0K rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K } SECTIONS { .nvic_vector : { *(vectors) /* Vector table */ } >rom .text : { *(.text) /* Program code */ *(.text.*) *(.rodata) /* Read only data */ *(.rodata.*) _DATAI_BEGIN = .; } >rom .data : { _DATA_BEGIN = .; *(.data) /* Data memory */ *(.data.*) _DATA_END = .; } >ram AT >rom .data_init : { _DATAI_END = .; } >rom .bss : { _BSS_BEGIN = .; *(.bss) /* Zero-filled run time allocate data memory */ *(COMMON) _BSS_END = .; } >ram .heap : { _HEAP = .; } >ram .stack : { _STACKTOP = .; } >stack }
Filename: low_level_init.c
#include <stdint.h> #include "main.h" void low_level_init(void); void nmi_handler(void); void hardfault_handler(void); extern unsigned int _STACKTOP; // Define the vector table unsigned int * the_nvic_vector[4] __attribute__ ((section(".nvic_vector")))= { (unsigned int *) &_STACKTOP, // stack pointer (unsigned int *) low_level_init, // code entry point (unsigned int *) nmi_handler, // NMI handler (not really) (unsigned int *) hardfault_handler // hard fault handler (let's hope not) }; extern unsigned int _BSS_BEGIN; extern unsigned int _BSS_END; extern unsigned int _DATA_BEGIN; extern unsigned int _DATA_END; extern unsigned int _DATAI_BEGIN; extern unsigned int _DATAI_END; void low_level_init(void) { uint32_t* bss_begin = &_BSS_BEGIN; uint32_t* bss_end = &_BSS_END; while(bss_begin < bss_end) { *bss_begin = 0; bss_begin++; } uint32_t* data_begin = &_DATA_BEGIN; uint32_t* data_end = &_DATA_END; uint32_t* datai_begin = &_DATAI_BEGIN; uint32_t* datai_end = &_DATAI_END; uint32_t data_size = data_end - data_begin; uint32_t datai_size = datai_end - datai_begin; if(data_size != datai_size) { //Linker script is not correct. while(1); } while(data_begin < data_end) { *data_begin = *datai_begin; data_begin++; datai_begin++; } main(); } void nmi_handler(void) { return ; } void hardfault_handler(void) { return ; }
Filename: main.c
#include <stdint.h> #include "main.h" unsigned int my_global_variable_zero = 0; unsigned int my_global_variable_one = 1; unsigned int my_global_variable_two = 2; int main(void) { int i=0; while(1) { i++; my_global_variable_zero++; my_global_variable_one++; my_global_variable_two++; } }
Filename: main.h
#ifndef __MAIN_H #define __MAIN_H int main(void); #endif // __MAIN_H
/Have fun.
Fatal error: Uncaught Error: Call to a member function link_ext() on null in /customers/9/b/5/fun-tech.se/httpd.www/stm32/class.page.php:62 Stack trace: #0 /customers/9/b/5/fun-tech.se/httpd.www/stm32/linker/index.php(752): page->getFoot() #1 {main} thrown in /customers/9/b/5/fun-tech.se/httpd.www/stm32/class.page.php on line 62