GNU Linker, can you NOT Initialize my Variable?

my students sometimes are afraid to ask questions, although I urge them ask any question. In my opinion there are no ‘dumb’ questions: only questioning things let us think and learn new things. I see that many readers of this blog are *not* afraid to comment or ask questions. The WordPress statistics shows 5’687 questions/comments for this blog (thank you all!), and the spam filter protected me from 202,341 items (ok, these *are* dumb) :-).

The ‘question of the week’ comes from Andy. That question caused me some serious head scratching, but the same time I have learned something important and useful for my next project: how to tell the ARM GNU linker *not* to initialize variables?

GNU ARM Embedded Linker Options

GNU ARM Embedded Linker Options

To Initialize or Not, That’s the Question: bss and data

I recommend to read my post about “text, data and bss: Code and Data Size Explained“. With that article I explain that

uint32_t globVariable;

will add 4 bytes to the bss section (uninitialized data).

And if I have initialized data like

uint32_t globInitializedVar = 0x12345678;

then this will add 4 bytes to the data section (initialized data), plus there will be 4 bytes allocated in FLASH for the initialization data (0x12345678).

My current appliation for a Freescale K20 gives me with printsize:

   text       data        bss        dec        hex    filename
  21004        288       3584      24876       612c    FRDM-K20_CDC.elf

So my FLASH size is 21004+288 bytes, and my RAM needs are 280+3584 bytes. So far, so good, and makes sense.

Not Initialized Data with Section __attribute__

The interesting thing is when I add this to my application (and of course reference/use it):

static unsigned char myBuffer[4096] __attribute__((section (".m_data_20000000")));

I’m using here a section attribute to allocate the variable in a named section, see “Defining Variables at Absolute Addresses with gcc“.

Then the sizes reported is:

   text       data        bss        dec        hex    filename
  21016       4384       3584      28984       7138    FRDM-K20_CDC.elf

Wow! The data size increased by 4K! Remember: that 4 KByte gets added to the FLASH for the initialization data. I would have expected that it would end up in the bss area, but it does not. So even as I have *not* initialized that variable, the linker creates a 4 KByte array of zeros in FLASH to initialize it :-(. Really surprising, unexpected and bad as it increases the FLASH footprint for no reason.

I was hoping that maybe ‘printsize’ is just wrong, but when I checked the two S19 files produced, there is indeed FLASH memory allocated for this 😦 :

4 KByte of Zeros for initialization data

4 KByte of Zeros for initialization data

Section Attribute and Data Initialization

So I started to dig more into how the __attribute__ section topic. And after googling and searching, I have found this (https://ohse.de/uwe/articles/gcc-attributes.html#var):

” You may only use the `section’ attribute with a fully initialized global definition because of the way linkers work. The linker requires each object be defined once, with the exception that uninitialized variables tentatively go in the `common’ (or `bss’) section and can be multiply “defined”. You can force a variable to be initialized with the `-fno-common’ flag or the `nocommon’ attribute.”

So this means that if I use the __attribute__ with section, the variable is treated like an initialized variable.

Not initializing data by linker: NOLOAD

The solution to this is to use (NOLOAD) in the linker script (http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_21.html):

SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  { contents } >region :phdr =fill
...
}

NOLOAD is described as

(NOLOAD)
The `(NOLOAD)’ directive will mark a section to not be loaded at run time. The linker will process the section normally, but will mark it so that a program loader will not load it into memory. For example, in the script sample below, the ROM section is addressed at memory location `0′ and does not need to be loaded when the program is run. The contents of the ROM section will appear in the linker output file as usual.

SECTIONS {
  ROM  0  (NOLOAD)  : { ... }
  ...
}

That’s the solution!

💡 Microchip does a nice job: Their compiler implements the ‘noload’ attribute at compile time with __attribute__((section (“SectionName”), noload)), but not GNU ARM 😦

So I add (NOLOAD) to my linker file section:

NOLOAD in Linker File

NOLOAD in Linker File

And with this, I get what I expect: the variable is added to bss and not to data:

   text       data        bss        dec        hex    filename
  21016        288       7680      28984       7138    FRDM-K20_CDC.elf

🙂

(NOLOAD for Battery Buffered RAM

The (NOLOAD) is needed as well if I have other kind of RAM I need to prevent from beeing initialized, such as battery buffered RAM. In such a case, I can have a linker file like this template:

MEMORY {
   NormalRAM: ORIGIN = ....., LENGTH = ....
   BattRAM  : ORIGIN = ....., LENGTH = ....
}

SECTIONS {
  ... other sections [.text, .data. ,bss, ....] here

   .uninit (NOLOAD) : { ...... } >BattRAM
}

Summary

Variables using the a named section attribute are treated by the linker like initialized varibles which consume FLASH memory for the initialization data. To prevent the linker from initializing the data, the (NOLOAD) attribute has to be used in the linker file (see http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_21.html). (NOLOAD) is needed as well for special memory areas like battery buffered RAM not to be erased.

Happy Not-Initializing 🙂

Advertisements

18 thoughts on “GNU Linker, can you NOT Initialize my Variable?

  1. Erich,

    Thank you for this article. I had just spent the final half of last Friday afternoon trying to find the answer for exactly this issue for some fixed RAM areas in my current project.

    The gcc linker documentation has the required information but lacks a lot of cross references needed to find it quickly.

    Peter

    Liked by 1 person

  2. Hi, thank you for digging into this. I just tried it and got *slightly* unexpected result: I get reported 0 for “data” (arm-none-eabi-size –totals), despite that there are initialized globals. The shared thing between “data” (where .data section and all initialized data contents (sub-sections?) ) reside and my custom section for non-initialized variables is same MEMORY (region) name.
    It seems that despite this report, the things are OK (arm-none-eabi-nm gives me):
    1ffffa10 D __data_end__
    1ffff8c0 D __data_start__
    where these are address names put before & after .data section & exported by my linker script. Probably my linker is not OK, because my .bin became 512M

    Like

  3. Thank you! Again (at least the third solution to problems during my early development with this new Kinetis processor and KDS, found on mcuoneclipse).

    Like

  4. the linker files supplied with KSDK2.1 had no “noinit” section defined , I discovered on sourceforge there is a linker file example for KLx that has a .noinit section using (NOLOAD)

    .noinit (NOLOAD):
    {
    . = ALIGN(4);
    _noinit = .;

    *(.noinit .noinit.*)

    . = ALIGN(4) ;
    _end_noinit = .;
    } > RAM

    /* Mandatory to be word aligned, _sbrk assumes this */
    PROVIDE ( end = _end_noinit ); /* was _ebss */
    PROVIDE ( _end = _end_noinit );
    PROVIDE ( __end = _end_noinit );
    PROVIDE ( __end__ = _end_noinit );

    I replaced “RAM” with “m_data” and then added the section attribute as per this example :
    unsigned char rebootflag __attribute__ ((section (“.noinit”))); // seems to work

    Like

  5. Thank you so much for this, you saved my day.

    I’m developing a project on STM32F4 development board with 8 megs of external SDRAM.
    Using VS2015 + VisualGDB + GNU c compiler. Since I added SDRAM support to my project, linking became 30x times slower (from 1sec to approx 30 sec). It’s just such a waste of time when you debug a lot!
    Being baffled for several days, I finally realized that it’s probably about initializing the arrays located in SDRAM section.
    Finally, addind (NOLOAD) to lds file did the job, linking is rocket-fast again.

    .esdram (NOLOAD) :
    {
    *(.ESDRAM*);
    } > ESDRAM

    Thank you so much!

    Like

  6. Pingback: Using Multiple Memory Regions with the FreeRTOS Heap | MCU on Eclipse

  7. Thanks – very useful information! I was able to successfully reduce the size of my elf and hex files using this approach. However when I use objcopy to create a binary file from the srec or elf build output, the binary file still apparently has the initialized m_data_20000000 section in it. I have several large static arrays that I manually place in the m_data_20000000 section using __attribute__((section (“.m_data_20000000”))). If I open the binary file I see a large section of 0’s. If I remove the arrays from my code the section of 0’s goes away.

    I’ve tried the ‘remove section’ option on objcopy, but it didn’t help. I’m confused as to how objcopy “recreates” this section of data given that it no longer appears in the srec/elf files that are being used to create the binary.

    Thanks for any insight you might be able to provide!

    Like

    • My view is that objcopy might not be a good fit for embedded projects. Binary files are not useful too, as they contain all bytes in a range. You better go with the SRec one, and if you want to create a binary from it, consider using the SRecord tool (see https://mcuoneclipse.com/2015/04/26/crc-checksum-generation-with-srecord-tools-for-gnu-and-eclipse/ or https://mcuoneclipse.com/2018/03/07/converting-binary-files-to-intel-hex-format-with-the-srecord-tool/)
      I hope this helps,
      Erich

      Like

      • Thanks for the pointers, Erich. My product does software updates over a Bluetooth interface so I was really hoping to shrink the file size and speed things up. I tried the srec tool but the binary file ended up being bigger than the one from objcopy. It’s just kind of frustrating that the reason the user has to wait longer is to send a bunch of zeros for memory that gets zeroed out anyway ;-). Thanks for your help.
        – Matt

        Like

        • By “no-init” I’m assuming you are referring to the (NOLOAD) parameter in the linker file? When I add it just like you show above the elf and srec file sizes drop dramatically and I can confirm that the section of 0 data in the srec file is gone. The binary file however is still large and has a section of 0’s. I’m generating the binary off of the new “no-init” srec file so not sure why it sill has the 0’s in there. Thanks for giving it some thought.

          Like

        • Hi Matt,
          yes, I’m referring to the NOLOAD. Do you have ‘gaps’ in your memory map? The S-Records and Intel hex file format does not emit the ‘gaps’ between memory areas, where the binary files do not have a sense of ‘start-address’ and size: binary files are a dump of memory from a start to an end/size. So if your memory has gaps between say flash areas, then they will be filled with zeros. I hope that makes sense?

          Like

        • Hi Erich,
          I am using the K21 part with 128K of RAM in the split ram configuration. My linker file allocates the two sections:
          m_data (RW) : ORIGIN = 0x1FFF0000, LENGTH = 0x0000FFFF
          m_data_20000000 (RW) : ORIGIN = 0x20000000, LENGTH = 0x0000FFFF
          I allow the linker to automatically place data in m_data so there may be some ’empty’ space between the end of that and the start of m_data_20000000. I also manually place a number of large arrays used for file system memory, audio buffers, etc. into the 20000000 RAM space. I think these arrays are what ends up getting initialized with zeros and bloating the size of my binary output file. It is frustrating because if I allow these arrays to be automatically placed in m_data, with the NOLOAD setting they don’t get initialized and don’t create huge blocks of zeros in the binary. But when they are manually placed in m_data_20000000, they do. Thanks.

          Liked by 1 person

        • Hi Erich –
          Wanted to give you a quick update. I looked at the section headers in the elf file using the readelf tool. Here is what I get:

          Section Headers:
          [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
          [ 0] NULL 00000000 000000 000000 00 0 0 0
          [ 1] .interrupts PROGBITS 00018000 0000f4 000188 00 A 0 0 4
          [ 2] .text PROGBITS 00018410 000280 02b150 00 AX 0 0 8
          [ 3] .data PROGBITS 1fff0000 02b3d0 0001a4 00 WA 0 0 4
          [ 4] .m_data_20000000 NOBITS 20000000 02b574 00db50 00 WA 0 0 4
          [ 5] .bss NOBITS 1fff01a8 02b578 00f534 00 WA 0 0 8
          [ 6] .romp PROGBITS 1ffff6dc 02b574 000024 00 WA 0 0 1
          [ 7] ._user_heap_stack NOBITS 1ffff700 02b598 000200 00 WA 0 0 1
          [ 8] .ARM.attributes ARM_ATTRIBUTES 00000000 02b598 000039 00 0 0 1
          [ 9] .debug_info PROGBITS 00000000 02b5d1 05f9f5 00 0 0 1
          [10] .debug_abbrev PROGBITS 00000000 08afc6 010661 00 0 0 1
          [11] .debug_loc PROGBITS 00000000 09b627 03085f 00 0 0 1
          [12] .debug_aranges PROGBITS 00000000 0cbe86 003548 00 0 0 1
          [13] .debug_ranges PROGBITS 00000000 0cf3ce 003b20 00 0 0 1
          [14] .debug_macro PROGBITS 00000000 0d2eee 04163d 00 0 0 1
          [15] .debug_line PROGBITS 00000000 11452b 037f57 00 0 0 1
          [16] .debug_str PROGBITS 00000000 14c482 0c2ba5 01 MS 0 0 1
          [17] .comment PROGBITS 00000000 20f027 000079 01 MS 0 0 1
          [18] .debug_frame PROGBITS 00000000 20f0a0 00829c 00 0 0 4
          [19] .shstrtab STRTAB 00000000 21733c 0000ed 00 0 0 1
          [20] .symtab SYMTAB 00000000 21779c 010750 10 21 3161 4
          [21] .strtab STRTAB 00000000 227eec 007325 00 0 0 1

          The m_data_20000000 section is clearly marked “NOBITS” so I’m not sure why the objcopy utility is filling the space with 0’s in the binary. I tried a newer version of objcopy (v2.28) and I get the same results.

          Since I have the size of the m_data_20000000 section from the map file, I figured out I can manually edit the binary file and remove the 0’s at the end of the file, which reduces my file size by about 1/3. Simple solutions to complex problems 🙂

          Like

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.