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:

” 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:

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. (NOLOAD) is needed as well for special memory areas like battery buffered RAM not to be erased.

Happy Not-Initializing 🙂

47 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

  8. Erich, thanks!

    I struggle with your syntax “.m_data_20000000 (NOLOAD) : AT(___symbol)”.
    I read linker file syntax to be:
    SECTIONS {

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

    }
    secname and contents are required.
    how come you are allowed to have no ?

    Like

  9. Thank you very much for the article. Programming on Texas Instruments MSP430 platform, using both GCC and a proprietary TI compiler (much better for this platform in my opinion) in different cases (I have a situation that forces me to use GCC in one source code). Due to your article I found very quickly that what TI compiler understands as type = NOINIT, with a slightly different syntax, GCC uses (NOLOAD) instead. Thanks for sharing!

    Like

  10. I have have a board with plenty of external SDRAM and plenty of external SPI Flash. The application will use many const images that I want to store in external Flash. In order to avoid delays during image drawing at runtime, I’m going to copy all the images from external Flash to external RAM at startup (an initial delay at startup can be accepted).

    Now I’m asking how to declare constant images in the source code. I’m thinking to have two different programs.

    A “loader” program with images in SDRAM and an application that copy images from SDRAM to external SPI Flash. I will launch this “loader” when, during development, SPI Flash content changes.
    The normal program will copy images from external Flash to SDRAM at startup.

    Now the problem is that SDRAM is a RAM section for the linker, so all the initialized variables put there have a ROM mirror space. At startup, C libraries copy initialized variables from ROM to RAM. In my case I don’t want to save initilized data of images in ROM, because they are saved in external Flash and copied in RAM by my application.

    I read your article about NOLOAD attribute of linker sections, however I think this doesn’t help in my case, do it?
    Could you suggest a way to instruct the linker to put initialized variables in RAM, but skipping the initialization process at startup (because my application will initilize them)?

    Like

  11. With information like this and Erich’s help, I got my SREC file size down from 400K to 280K this morning. I did find that using “__NOINIT(RAMSECTION)” worked to stop the areas being initialized.

    Like

  12. I was having trouble with a flash programmer getting very upset trying to program RAM based sections into flash because they were in the .elf file. (NOLOAD) is exactly what I needed to fix the issue. Thanks for writing this up!

    Liked by 1 person

  13. Hello
    I work on a system powered by batteries.
    I use the NOLOAD to “protect” a section of the RAM so I can keep some information between resets. (It simulates static RAM).
    In my current use cases, I don’t care about the initial values (at the very first boot) of the data in this RAM section. Basically I have some code that checks if the data are in some valid ranges of values, if it’s the case it’s fine, else the variable are initialized.
    But there is one question I can’t solve: is it possible to know the initial value of variables in such section, for static variables can we rely on the fact that the initial value is 0 at the very first boot?
    I guess we can’t but I would like some feedback.
    For your information, my use case is to store some kind or runlevel and keep it over reset, but i need to init it correctly at the very first boot (0 could be fine).
    Regards.

    Liked by 1 person

    • Hi Jean-Francois,
      it very much depends on the technology used for your RAM. It can be anything (all zero, all one, or even random) I think. The RAM I have used were all zero after a complete power down, but if the power down was not complete, some cells still could have some values. I think you need to have some checksums (CRC, etc) to verify if the data is valid, maybe over different sections. If not valid, erase it.

      Like

      • [EARLY POST] Here’s my code:
        if (ramPowerUpCheck!=0x12345678)
        {
        ramResetData[0] = 0; /* Not previously powered – clear reset counts */
        ramResetData[1] = 0;
        ramResetData[2] = 0;
        ramResetData[3] = 0;
        ramPowerUpCheck = 0x12345678;/* And indicate that we *are* powered */
        }

        Liked by 1 person

  14. Thanks for this solution.
    I want realize a custom bootloader, and use an non initialized variable to understand when the app jump to bootloader, setting this variable with a “magic number”.
    So, when I turn the power on the bootloader read this variable with a “xxx” value (and can jump to app). But in the APP i can set this variable to magic number and be sure that bootloader dont jump back to APP.
    Can this be a good solution? Yes, random initialization can be = to magic number, but qith a 4 byte vars…
    (note: I’m using STM Cube IDE)
    Thanks!

    Liked by 1 person

    • Yes, that’s an approach I’m using in some applications too. Such a ‘magical number’ allows me to know if the memory has been initialized or not. Actually I’m not using a number, but a record with information about the application, protected with a CRC: if the version numbers are valid with the correct CRC, then I know the application was running earlier in case it is not possible for example to know the reset reason (power-on reset, watchdog reset, brown-out reset, etc).

      Like

      • Could someone help me? Still have problems with link scripts. Variable is initializated to 0, donno how…
        In main.c:
        static uint32_t BootNoInit __attribute__((section (“.boot_noinit”)));
        In .ld:
        _MRamStart = 0x20001BFF0;
        _MRamEnd = 0x2001BFFA;
        MEMORY
        {
        RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 111K
        NOINITRAM (xrw) : ORIGIN = 0x2001BFF0, LENGTH = 10

        }
        .noinit (NOLOAD):
        {
        . = ALIGN(4);
        _MRamStart = .;
        /* nothing here!*/
        _MRamEnd = .;
        . = ALIGN(4) ;
        } >NOINITRAM
        .mySegment1 _MRamStart : {KEEP(*(.boot_noinit))}
        Thanks in advance!

        Like

        • I use MCUXpresso and its linkscripts … in my C source code I write this, which sets up the variable to be non-init:
          extern_ byte __NOINIT(FIXEDBOOT_DATA) ramFlashData[16];
          The __NOINIT provided in cr_section_macros.h is defined:
          #define __NOINIT(bank) __SECTION(noinit, bank)

          In the MCUXpresso linkscripts, this is written; looks similar to yours but maybe there’s a subtle difference?
          /* DEFAULT NOINIT SECTION */
          .noinit (NOLOAD): ALIGN(${bss_align})
          {
          _noinit = .;
          PROVIDE(__start_noinit_${memory.alias} = .) ;
          PROVIDE(__start_noinit_${memory.name} = .) ;

          *(.noinit*)
          . = ALIGN(${bss_align}) ;
          _end_noinit = .;
          PROVIDE(__end_noinit_${memory.alias} = .) ;
          PROVIDE(__end_noinit_${memory.name} = .) ;
          } > ${DATA} AT> ${DATA}

          Liked by 1 person

        • Found the issues, leave here the solution for others guys…
          It was in the initialization:
          _MRamStart = 0x20001BFF0; is too long: 0x2001BFF0; (one extra zero, a typo!)
          if I change it to
          MEMORY
          {
          NOINITRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16 /* or use _MRamStart! */
          RAM (xrw) : ORIGIN = 0x20000010, LENGTH = 111K

          It work!
          Donnow if it is a problem of STM or other, but I saw variable “BootNoInit ” loaded with different value a run command (set to 3 at the beginning, read 10 in a random point). Probably the variabloe share the memory with heap (?) or stack.
          In other words, I move the “BootNoInit” var from the end of RAM to the beginning, and it work!
          Thanks to Ian C. and all the guys.

          Liked by 1 person

  15. Hi,
    are you sure those 4kB of zeroes are added to flash?

    In the same situation with gcc 9.2.1 adding NOLOAD attribute I see the size shift from .data to .bss but the binary .bin file is exactly the same size.
    The NOLOAD .hex file removes instructions to fill my section, starting in ram at 0x20078000, with zeroes (see below) but I don’t think they actually take flash size on the micro.

    When running the executable in both cases the section is not zero initialized (we use this to retain RAM information between resets).

    :020000042007D3
    :108000000000000000000000000000000000000070
    :108010000000000000000000000000000000000060
    :108020000000000000000000000000000000000050
    :108030000000000000000000000000000000000040
    :108040000000000000000000000000000000000030
    […]

    Like

What do you think?

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