Putting Code of Files into Special Section with the GNU Linker

The GNU Linker (ld) is very, very powerful. This time I wanted to put all my Processor Expert generated code into its own dedicated section. This is useful for example to have a bootloader or a library inside a special area in FLASH. It was not obvious to me how to do this with the linker, with some search on the internet and some trial and errors, I finally managed that. And as always with exploring things, I have learnt something :-). So here is how I’m able to put the code of arbitrary files into its own dedicated section.

Code Section for Generated Code

Code Section for Generated Code

For this, I need to change the linker file. As in my setup Processor Expert is generating it, I need to disable it so it does not change it anymore. This is a setting in the CPU component, under Build Options:

Disabled Generation of Linker File in Processor Expert

Disabled Generation of Linker File in Processor Expert

Next, I’m modifying the linker file, and creating a new section (m_text2) where I want to put my dedicated code:

Modified Linker File with new Section

Modified Linker File with new Section

Next, I’m extending the linker file with my placement. For example to place the Cpu.o into my dedicated section, I use a new placement .generatedCode just before the .text placement:

  .generatedCode : 
  { 
    . = ALIGN(4);
    *Cpu.o (.text .text*) 
    . = ALIGN(4); 
  }  > m_text2 /* new section for my special code files */ 

  /* The program code and other data goes into INTERNAL_FLASH */
  .text :
  {
   ...

Note that I’m using ‘*Cpu.o’, that means that any object file matching this will end up in this section. So if I want to have all the object files from the Processor Expert Generated_Code folder, I simply can use this:

  .generatedCode : 
  { 
    . = ALIGN(4);
    *Generated_Code/*.o (.text .text*) 
    . = ALIGN(4); 
  }  > m_text2 /* new section for my special code files */

So this might be already all what you need.

However, if you get linker errors about duplicated symbols, then it means that the linker is pulling in things twice. To avoid that objects get linked twice, I use the EXCLUDE_FILE directive, with a modified original .text and .text* placement:

  .generatedCode : 
  { 
    . = ALIGN(4);
    *Generated_Code/*.o (.text .text*) 
    . = ALIGN(4); 
  }  > m_text2 /* new section for my special code files */ 
  
  
  /* The program code and other data goes into INTERNAL_FLASH */
  .text :
  {
    . = ALIGN(4);

    /* original placement: */
    /* *(.text)  */         /* .text sections (code) */
    /* *(.text*) */          /* .text* sections (code) */

    /* modified placement: need to exclude the ones I have in .generatedCode above */
    *(EXCLUDE_FILE(*Generated_Code/*.o).text .text*)  /* .text and .text* sections (code) */

    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);

    _etext = .;        /* define a global symbols at end of code */
  } > m_text

💡 It seems that EXCLUDE_FILE can only be used once in the linker file. All objects must be inside the (…) following the EXCLUDE_FILE directive.

Below another example (contributed by TsiChung, thank you!) which shows how to exclude certain object files not be linked: with adding .constdata to the list prevents things linked to RAM area.

.text : ALIGN(4)
{
   *(EXCLUDE_FILE(*my_wifi/*.o *lwip/*.o).text* .rodata .rodata* .constdata .constdata*)
   /* other content here */
} > FLASH

.data : ALIGN(4)
{
  /* other content here */
  *my_wifi/*.o(.text .text* .rodata .rodata*)
  /* other content here */
} > SRAM AT>FLASH

And in the linker .map file I can verify that things worked:

Code of Files placed in dedicated section

Code of Files placed in dedicated section

Happy Linking 🙂

29 thoughts on “Putting Code of Files into Special Section with the GNU Linker

  1. Please forgive my ignorance. I understand the concept you describe here, and it is interesting.

    What I don’t understand is under what conditions would it be preferable to do things this way. What problems does it solve for the programmer?

    Thank you.

    Like

    • Say if you envision that you very likely might need to patch some code from some source files in our device later on, it is not a bad idea to have that ‘preliminary’ code in a certain section of the memory together, so you only will need to patch that area of memory.
      The other use case is if you have special code which e.g. needs to be protected or dealt in a special way in your device, you can place it into that special area. I agree that you can do this with section attributes in your code, but doing it on a file by file base is a nice other way to achieve this.

      Like

  2. Is it possible to place shared ARM GCC library objects into a custom memory section?
    When I open my project’s map file, I see the linker automatically place math lib functions (__aeabi_frsub, __mulsf3, etc_) at the start of .txt section. Then toward end of the same section after placement of my own app code, the linker places standard libray ojects such as libc.a. I want to move those objects to a special defined memory section which I specified in the linker script (.ld) file.

    Like

    • In that article you are using a powerPC compiler? The error message indicates that it cannot find the object file. Have you added that path to the object file to the search path (libraries/etc) for the linker?

      Like

  3. Pingback: Execute-Only-Code with GNU and gcc | MCU on Eclipse

    • Yes, I have used it with STM32F304, but STM32F7xx should not be different. I think you might have placed it in the linker file into areas which are not mapped to RAM or FLASH?

      Like

  4. We have a product that we just added external SRAM to.
    Through a process similar to what is shown here, I defined a memory range in the linker script for the external SRAM and I’m able to get variables and arrays placed in it.

    Now my problem is being able to use external SRAM just like internal SRAM – placing whatever variables and arrays into it that I want. Specifically:

    uint8_t xsram_init_buf[4] __attribute__((section (“.x_sram”))) = { 1,2,3,4 };
    uint8_t isram_init_buf[4] ={0x01, 0x02, 0x03, 0x04};

    In this case, xsram_init_buf[] is not properly initialized in the startup code (before main()), but isram_init_buf[] is properly initialized.

    The code that does the initialization looks like this:
    pSrc = &_etext;
    pDest = &_srelocate;

    if (pSrc != pDest) {
    for (; pDest < &_erelocate;) {
    *pDest++ = *pSrc++;
    }
    }

    So it will only work if all the data values to use for initialization are in one contiguous block of FLASH and all the variables to be initialized are in one contiguous block of RAM. It seems like I'd need at least two groups – one for internal SRAM and one for external SRAM.

    Is there a way to get the compiler to group the initialization information into two sets?

    Like

    • What toolchain/IDE are you using? I think your startup code might not be correctly implemented. The linker creates a list of copy down blocks, each with a destination address and a source block with the data to copy from.
      Then the startup code goes through that list of blocks and initilizes each block. This is done for example in the MCUXpresso SDK startup code:

      void data_init(unsigned int romstart, unsigned int start, unsigned int len) {
      unsigned int *pulDest = (unsigned int*) start;
      unsigned int *pulSrc = (unsigned int*) romstart;
      unsigned int loop;
      for (loop = 0; loop < len; loop = loop + 4)
      *pulDest++ = *pulSrc++;
      }

      The above gets called like this:

      // Load base address of Global Section Table
      SectionTableAddr = &__data_section_table;

      // Copy the data sections from flash to SRAM.
      while (SectionTableAddr < &__data_section_table_end) {
      LoadAddr = *SectionTableAddr++;
      ExeAddr = *SectionTableAddr++;
      SectionLen = *SectionTableAddr++;
      data_init(LoadAddr, ExeAddr, SectionLen);
      }

      I hope this helps,
      Erich

      Like

      • Thanks Erich.

        It’s the ARM GNU toolchain. Compiler is arm-none-eabi-gcc.exe and Linker is arm-none-eabi-g++.exe. The IDE is Atmel Studio, which is based on Microsoft Visual Studio.

        So is there a linker command or argument that formats the copy down information into blocks like this?

        Like

        • The GNU linker creates such a list automatically. I think your Atmel Studio startup code for your device is wrong and not properly handling such a list?

          Like

        • With considerable help from the chip vendor, I was able to get this working where globals in external SRAM would be initialized during startup.

          The main trick is that the .ld file needed to be modified like this:
          /* external SRAM section */
          .xsram_init : AT ( LOADADDR(.relocate) + SIZEOF (.relocate) )
          {
          . = ALIGN(4);
          _xsram_ram_init_start = .;
          *(.x_sram_init*);
          . = ALIGN(4);
          _xsram_ram_init_end = .;
          } > xsram
          _xsram_flash_init_start = LOADADDR(.xsram_init);

          .xsram_no_init (NOLOAD) : AT ( LOADADDR(.xsram_init) + SIZEOF (.xsram_init) )
          {
          . = ALIGN(4);
          _xsram_ram_clear_start = .;
          *(.x_sram_no_init*);
          . = ALIGN(4);
          _xsram_ram_clear_end = .;
          } > xsram

          And the code to do the initialization during startup looks like this:
          // Initialize the XSRAM segment
          pSrc = &_xsram_flash_init_start;
          pDest = &_xsram_ram_init_start;
          if (pSrc != pDest) {
          for (; pDest < &_xsram_ram_init_end;) {
          *pDest++ = *pSrc++;
          }
          }

          Then for globals that I wanted to be placed in external SRAM and initialized:
          __attribute__((section(".x_sram_init"))) uint32_t var_init = 0x12345678;

          For those that I don't want to be initialized at all:
          __attribute__((section(".x_sram_no_init"))) uint32_t var_no_init;

          Like

  5. Pingback: Placing Code in Sections with managed GNU Linker Scripts | MCU on Eclipse

  6. As per linker documentation of input section , following command only exclude ‘ .text ‘ input sections of all the *Generated_Code/*.o & will include all ‘ .text.*’ input sections of all files including files from *Generated_Code/*.o

    *(EXCLUDE_FILE(*Generated_Code/*.o).text .text*)

    It should be

    *( EXCLUDE_FILE(*Generated_Code/*.o) .text EXCLUDE_FILE(*Generated_Code/*.o) .text* )

    Please confirm

    refer
    https://sourceware.org/binutils/docs/ld/Input-Section-Basics.html

    Liked by 1 person

What do you think?

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