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.
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:
Next, I’m modifying the linker file, and creating a new section (m_text2) where I want to put my dedicated code:
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:
Happy Linking 🙂
Thanks Erick. Have you used the Kinetis Bootloader at all?
http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=KBOOT
LikeLike
No, I have not looked at that bootloader yet, because it does not not support the KL25Z. I have developed my own bootloader for my use case: https://mcuoneclipse.com/2013/04/28/serial-bootloader-for-the-freedom-board-with-processor-expert/
LikeLike
great as always!
Have you try relocate just variable, like table of uint8_t or any?
LikeLike
Hi Greg,
no, I have not tried that, but the same thing should work for data too. Instead of specifing the code sections, use the data sections.
LikeLike
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.
LikeLike
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.
LikeLike
Thank you.
LikeLike
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.
LikeLike
I would think that should be possible, but I have not done that myself. The easiest way to get control over this is if you would re-build the library yourself, then you can have them the way you want it.
LikeLike
You can use commands like these in your linker file at the start of the text section:
*libc.a:(.text*)
*libm.a:(.text*)
(note the colon after the library file name)
LikeLike
Hi Henry,
thanks for that hint, that’s very useful! Where did you find this documented? Somehow I have not spotted this in the linker/linker file documentation?
LikeLike
Hi Erich,
That’s the syntax for matching files within archives, it’s documented here: https://sourceware.org/binutils/docs-2.24/ld/Input-Section-Basics.html#Input-Section-Basics
In the case I mentioned, an archive file name with nothing after the colon matches the whole archive.
LikeLike
Thanks for the clarification!
LikeLike
Hi Erich,
I can’t understanding of this operation by follow you step.
This is my question on the nxp community.
I have tried to follow you step, but it didn’t work.
Could you give me some help?
https://community.nxp.com/thread/475729
LikeLike
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?
LikeLike
Pingback: Execute-Only-Code with GNU and gcc | MCU on Eclipse
Hi,
does this work for STM32F7xx? I tried this and ended up in MemManage_Handler.
LikeLike
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?
LikeLike
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?
LikeLike
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
LikeLike
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?
LikeLike
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?
LikeLike
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;
LikeLike
Thanks for sharing this, this will be very helpful if I ever will need it.
LikeLike
Pingback: Placing Code in Sections with managed GNU Linker Scripts | MCU on Eclipse
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
LikeLiked by 1 person
I can say that the above way worked in my case and version of ld, but the second syntax makes sense according to the documentation, so it won’t hurt if you use the second way.
LikeLike