How do I know where the .data section needs to get the init data from? (gcc linker)

Johan picture Johan · Feb 15, 2012 · Viewed 7k times · Source

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.

Answer

ams picture ams · Feb 15, 2012

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