When building a gcc based bare metal mcu project you need to take care of the initialization of the .data and .bss sections during startup.
The .bss section is quite easy since I just fill the entire section to 0. But variables in the .data section needs to have their initialization data in rom/flash and copied during startup.
How do I know where the data with the initialization values can be found?
Let's take a example.
Let's say that I create two global variables in main.c
unsigned int my_global_variable_one = 1;
unsigned int my_global_variable_two = 2;
Then I can use objdump on the object file to see in what section they will be in, but I can't find anything in the objdump out put where the init data should be placed.
$ arm-none-eabi-objdump --syms main.o | grep my_global_variable
00000000 g O .data 00000004 my_global_variable_one
00000004 g O .data 00000004 my_global_variable_two
Then I can look at the resulting elf for the entire system, in this case main.elf.
$ arm-none-eabi-nm -n main.elf | grep my_global_variable
20000000 D my_global_variable_one
20000004 D my_global_variable_two
Where can I find where they are so I can do copy the data? What do I need to put into my linker script?
It should be in something like .text or .rodata, but how do I know?
How can I check where the init data for my_global_variable_one is?
Can I find where this data is with any of the binutils commands like readelf or objdump?
/Thanks
This is on a stm32 (Cortex M3) mcu, and the CodeBench version of gcc is used.
The compiler puts all code and some read-only data in the .text
section. There may also be a .rodata
section. You can have your linker script put that in a ROM address something like this:
. = <rom-base-address>;
.rodata : { *(.rodata) }
<other read-only sections go here>
.text : { *(.text) }
The compiler puts all the initial values of writable data in the .data
section, and all symbols that don't have an initial value in .bss
. The .bss
is easy, you just place that in RAM. The .data
wants to be in RAM at run time, but in ROM at load-time, and the linker script lets you do that with the AT
keyword:
. = <ram-base-address>;
.bss : { *(.bss) }
.data : AT ( ADDR (.text) + SIZEOF (.text) )
{ *(.data) }
This means that the data section now has a different LMA and VMA (Load Memory Address / Virtual Memory Address). Your program will expect to find the data at the VMA (the run-time address) but the data will actually exist at the LMA (you may have to teach your flasher to do this), which is right after the .text
section.
If you need to figure out where to copy to and from then you can create pointers in the linker script. So, modify the above example like this:
.data : AT ( ADDR (.text) + SIZEOF (.text) )
{ _data_lma = LOADADDR(.data); _data_vma = .;
*(.data);
_data_size = SIZEOF (.data);}
You can then do memcpy (_data_vma, _data_lma, _data_size)
in your start-up code (although you might have to hand-code that in assembler?) Those symbols will appear to your program as constant globals that resolve at link time. So, in your assembler code you might have something like:
_data_lma_k:
.long _data_lma
Then the number will be hard coded into the .text
section. Of course, the assembler syntax might be different on your platform.
For more information, see the linker manual here