Cleaning Out Unwanted Memory Sections for ARM Cortex-M
Sometimes we want to individualize a piece of hardware. We don’t want to recompile the whole program (because then it’s hard to verify that it is the same program), and we don’t want to rewrite everything in memory. How do you do that in C? Depending on the processor you’re using it is as simple as telling it to put the data structure at a given address, compile and write. When working with the ARM Cortex-M, and the GNU compiler, it’s not that simple. I found that out when trying to write 24 bytes to a specific memory location.
What we’re writing
Here’s the genericized data structure:
uint32_t structVersion; //ID to tell which type of struct this is.
uint32_t member1;
uint32_t member2;
uint32_t member3;
uint16_t member4;
uint8_t member5;
uint8_t member6;
uint8_t member7;
} Provisioning;
How to get it in the Right Place
There’re a couple ways to do this, but the way I chose was to create a specific section of memory for this, called provision. To put my data structure in that memory location, I created it in its own .c file and prefaced it with
Your header files may have a macro to make this easier.
Include your header file that has the structure definition and that’s it. You’ll probably want to do a better job at defining the structure.
The second half of it is creating the memory location to make sure that you don’t overwrite other areas of memory. To do this, you need to see how your flash memory is designed in your chip. The one used here, an NXP LCP5528, which has 512 byte sectors, which are the smallest region of memory that can be cleared. This means that to write one byte in the bank, the whole bank needs to be cleared. I had space to spare, so I defined the provision region, in the MEMORY section of the linker file, to be 1 kB as so:
I made sure to remove this from the larger memory section I carved this out of. Please note that the memory is listed as r, which means that it can be read by the program.
Keeping What you don’t Use
The next challenge is to create the section and make sure it keeps the data, since the compiler knows it’s never accessed in this project. To do this we create the .provision section.
{
provisioning_start = .;
*(.provision*)
KEEP(*(.provision*))
. = ALIGN(4);
} > PROVISION_FLASH
PROVIDE(end = .);
The KEEP statement is important because the compiler will throw out the data structure otherwise because it’s not referenced in this project. In the main program, we just need to create the symbol in its linker file as so:
Discarding What you don’t Want
This is the fun part. After getting the data structure and linker file ready, my first compile had kilobytes of data in it, and it was all over the memory map. Here’s the linker output:
.text 80 0
.init 4 80
.fini 4 84
.data 476 536870912
.data_RAM2 0 67108864
.data_RAM3 0 1074790400
.data_RAM4 0 537133056
.bss 180 536871388
.text 15636 88
.provision 24 523264
.uninit_RESERVED 0 536870912
.noinit_RAM2 0 67108864
.noinit_RAM3 0 1074790400
.noinit_RAM4 0 537133056
.noinit 0 536871568
.heap 4096 536871568
.heap2stackfill 4096 536875664
.stack 0 537063424
.ARM.attributes 54 0
.comment 73 0
.debug_frame 5288 0
.debug_info 347 0
.debug_abbrev 149 0
.debug_aranges 24 0
.debug_line 185 0
.debug_str 768 0
Total 31476
We need to get rid of the extra stuff because it will overwrite other memory locations.
Looking at the linker documentation, we find that there are standard libraries that we can get rid of. To do this we need to give the linker the following options, -nostartfiles, -nodefaultlibs, -nostdlib, -nolibc, and -s. This helps a lot.
Linker Options | Total Size (bytes) |
Starting | 31476 |
-nostartfiles | 31466 |
-nostartfiles, -nodefaultlibs | 31466 |
-nostartfiles, -nodefaultlibs, -nostdlib | 31466 |
-nostartfiles, -nodefaultlibs, -nostdlib, -nolibc | 31466 |
-nostartfiles, -nodefaultlibs, -nostdlib, -nolibc, -s | 24705 |
Everything and removing -lc | 23349 |
Everything and removing -lc, -g, -lrdimon | 23349 |
Everything and removing -lc, -g, -lrdimon, -u _printf_float | 8419 |
That was about as far as I could go with command line options to the linker. Here’s what the linker output now looks like:
.text 80 0
.data 0 536870912
.data_RAM2 0 67108864
.data_RAM3 0 1074790400
.data_RAM4 0 537133056
.bss 0 536870912
.text 0 80
.provision 24 523264
.uninit_RESERVED 0 536870912
.noinit_RAM2 0 67108864
.noinit_RAM3 0 1074790400
.noinit_RAM4 0 537133056
.noinit 0 536870912
.heap 4096 536870912
.heap2stackfill 4096 536875008
.stack 0 537063424
.comment 73 0
.ARM.attributes 58 0
Total 8419
As you can see, there are not nearly as many sections in there, but there are some we still need to remove. To do this, we go back to the actual linker file.
In the linker file, we define the
What we are writing is the vector table as the first block of memory:
.text : ALIGN(4)
{
FILL(0xff)
__vectors_start__ = ABSOLUTE(.) ;
/* Global Section Table */
. = ALIGN(4) ;
__section_table_start = .;
__data_section_table = .;
LONG(LOADADDR(.data));
LONG( ADDR(.data));
LONG( SIZEOF(.data));
LONG(LOADADDR(.data_RAM2));
LONG( ADDR(.data_RAM2));
LONG( SIZEOF(.data_RAM2));
LONG(LOADADDR(.data_RAM3));
LONG( ADDR(.data_RAM3));
LONG( SIZEOF(.data_RAM3));
LONG(LOADADDR(.data_RAM4));
LONG( ADDR(.data_RAM4));
LONG( SIZEOF(.data_RAM4));
__data_section_table_end = .;
__bss_section_table = .;
LONG( ADDR(.bss));
LONG( SIZEOF(.bss));
LONG( ADDR(.bss_RAM2));
LONG( SIZEOF(.bss_RAM2));
LONG( ADDR(.bss_RAM3));
LONG( SIZEOF(.bss_RAM3));
LONG( ADDR(.bss_RAM4));
LONG( SIZEOF(.bss_RAM4));
__bss_section_table_end = .;
__section_table_end = . ;
/* End of Global Section Table */
*(.after_vectors*)
} > PROGRAM_FLASH
If we get rid of that section, we are now down to just writing the data structure when we look at the .hex file.
What’s with all the Other Stuff?
If we want to remove the other sections we can remove most of them by removing their definitions in the linker file. There are a few sections that doesn’t work for. For those we need to
{
*(.comment*)
*(.ARM*)
}
Putting it all together, here’s the linker output:
.provision 24 523264
Total 24