Linker script change explodes .bin size on Cortex-M7? - Page 1
Forum section: electronics / general

author: mlamoore
author_rank: Newbie
timestamp: August 10, 2022, 10:48:03 pm
content: TL;DR: What determines which sections from my linker script go into my .bin file? What else could cause a .bin file to explode in size?I'm using a STM32H743 with many separate regions of RAM. (See ST RM0433 section 2.3.2 and 2.1 for memory map and system architecture if you're curious.)Our current linker script uses the 512kB AXI SRAM for all of the default RAM segments (.bss, .data, etc). We also have memory regions SRAM1, SRAM2, SRAM3 and are successfully using __attribute__((section(".sram1"))) to put DMA buffers into those regions of RAM. That's working great, no problems.However, I want to update the linker script to take advantage of the 128kB DTCM. I want to make DTCM the default type of RAM, explicitly moving large buffers and structures that aren't performance critical to AXI SRAM so everything else fits in DTCM.When I did this, the .bin file size increased from 412kB to 459 Megabytes, which completely breaks our bootloading setup. I reverted most of my changes and found that if I move the .data section from AXISRAM memory to DTCMRAM and use the new .axisram section by adding __attribute__((section(".axisram"))) to some large structures that would have been in .bss otherwise, the .bin grows from 412kB to 459MB. If I just use the .axisram section it grows from 412kB to 508kB, and if I just move .data to DTCM it shrinks from 408kB to 412kB, but if I both move .data and put some .bss data into .axisram, then the size grows from 412kB to 459MB.Also interesting is that the memory map shows flash to AXISRAM is 448MB apart.Code:.axisram :{. = ALIGN(4);_startAXISRAM = .;*(.axisram);*(.axisram*);. = ALIGN(4);_endAXISRAM = .;} >AXISRAM

author: westfw
author_rank: Super Contributor
timestamp: August 11, 2022, 12:40:55 am
content: .bin files don't have any section info in them. AFAIK, they start at some particular address (perhaps always 0?) and include every byte till the "end" of initialized data. If you have initialized data in two ARM Cortex sections that are 24MB apart, you'll get a .bin file that has nearly 24MB of useless fill in between the two pieces.You don't want initialized data in the RAM part of a .bin file, do you? Initialized RAM data on microcontrollers is almost always relocated to flash and then copied to RAM by the startup code...

author: cv007
author_rank: Frequent Contributor
timestamp: August 11, 2022, 05:35:26 am
content: I would guess you are forgetting to give a section a load address (AT). If any initialized data ends up in a section without a load address, it will be located at the region's virtual address which means its now data that is at an address many MB's away from the typically 0 based flash region, so to get a complete bin file the space in between also needs to be filled.I don't use multiple memory regions, but imagine if you wanted to have multiple ram regions and be able to both initialize and zero clear each region/section, then you will also need to create symbols in those sections that will be used by your own startup code to initialize data if needed (load address also needed for the section to place the init data in flash), and/or to zero clear bss type data.

author: hans
author_rank: Super Contributor
timestamp: August 11, 2022, 07:14:17 am
content: bin files are indeed a continuous memory dump without segmentation, and doesn't have to start 0x0. PIC32s also clasically have this problem because of their bootloader FLASH. If you try to make a bin with the default GCC tools, you'll get one that's potentially 1GB big. There even is a dedicated elf2hex executable in XC32, because hex does allow data to placed at arbitrary memory addresses.Back to ARM. Here is a linker script for a STM32L431 ld I picked (note: this is ST stock code):Code:/* used by the startup to initialize data */_sidata = LOADADDR(.data);/* Initialized data sections goes into RAM, load LMA copy after code */.data :{. = ALIGN(8);_sdata = .; /* create a global symbol at data start */*(.data) /* .data sections */*(.data*) /* .data* sections */. = ALIGN(8);_edata = .; /* define a global symbol at data end */} >RAM AT> FLASH/* Uninitialized data section */. = ALIGN(4);.bss :{/* This is used by the startup in order to initialize the .bss secion */_sbss = .; /* define a global symbol at bss start */__bss_start__ = _sbss;*(.bss)*(.bss*)*(COMMON). = ALIGN(4);_ebss = .; /* define a global symbol at bss end */__bss_end__ = _ebss;} >RAMAs you can see, .data is itialized and specifies that the initial RAM contents are stored in FLASH. However, .bss is uninitialized and so does not. The startup code for loading constants from FLASH to RAM looks something like:Code:CopyDataInit:ldrr3, =_sidata /* <<< FLASH source address pointer */ldrr3, [r3, r1]strr3, [r0, r1]addsr1, r1, #4LoopCopyDataInit:ldrr0, =_sdata /* <<< RAM destination start */ldrr3, =_edata /* <<< RAM destination end */addsr2, r0, r1cmpr2, r3bccCopyDataInitSo for your problem, the easiest 'fix' I guess is to relocate buffers and not them preinitialize them in C (no value assignments). This prevents the linker from putting data there. Then you start using them, you could use memset()If you do need those initial pre loaded with values, you'll probably have take note of the FLASH address of the generated image file (such as `sidata`), do a similar "AT >FLASH" structure for that section and record the destination start/end RAM address, and write your own startup memcpy loop.To be honest it's not hard, but it's a bit fiddly to get right.For that STM32L431 example, I had modified the startup script to move the program code (sections .text and .rodata) from FLASH to RAM1, copied it and booted with that address. By saying to the compiler it must link addresses for RAM1 and store the data in FLASH, all memory loads, program jumps, etc. are also correctly linked for RAM1. By running code from SRAM, I was able to power off FLASH on the chip and save a considerable amount of power.

author: eutectique
author_rank: Frequent Contributor
timestamp: August 11, 2022, 12:22:29 pm
content: A side note (as the startup code has been mentioned): there is no requirement to write it in assembly. You can use C. Less chances to mess the branches and labels.

author: Siwastaja
author_rank: Super Contributor
timestamp: August 11, 2022, 12:26:23 pm
content: As others say, converting a file which supports discontinuous (sparse) address space like .elf or .hex to a raw binary, tools can't do anything else but to fill in blanks with zeroes. So the solution is to remove all sections outside your intended contiguous area (either by fixing the script so that such sections are not generated to begin with, or if that seems tricky, by removing them while outputting binary:arm-none-eabi-objcopy -Obinary --remove-section=.ARM* --remove-section=*bss* --remove-section=.storage main.elf main.binWhat is .ARM*? I have no idea, Something (has to be either compiler or linker) was generating sections under such names, I'm not interested in whatever crap they are, so I just remove them (I don't remember if I ever Googled what they are supposed to be.)

author: mlamoore
author_rank: Newbie
timestamp: August 11, 2022, 12:40:33 pm
content: My full initial test has separate .axisram and .axi_initialized sections, only using AT >FLASH_APPLICATION for .axi_initialized. I'm not intending to put any initialized data into .axisram, only .axi_initialized.The first thing in main is a function call that loads the vector table and critical interrupt routines from flash into ITCM based on symbols from the linker script, and that's working fine. The new .axi_initialized section will be loaded there too, but I commented that out for now while testing my minimized issue.As far as I can tell with find all, only large static structs with no initializer should be placed in the .axisram section, and the .data section is using AT >FLASH_APPLICATION and being loaded properly (as is everything else that needs initial values).Is there a 'best' way to explicitly tell the linker to not include memory regions? Some googling suggests I could try NOLOAD and manually zero out those sections of RAM in the same function where I load data from flash into RAM, I'll try that later today.Calling readelf on my computer output showed the AW attributes on the new .axisram region as well as the SRAM1-3 regions, and it showed LOADBITS for all of those (despite only being used on static variables with no initial values) as opposed to NOBITS for .bss.Honestly, looking at the readelf output I'm more surprised that I didn't already have this issue with SRAM1-3 which look the same as this new section in the linker script and in readelf and are further away from flash in the memory map...

author: mlamoore
author_rank: Newbie
timestamp: August 11, 2022, 02:16:17 pm
content: Adding (NOLOAD) to linker sections that shouldn't be in the .bin fixed this issue. I have no idea why I wasn't having the exact same problem with my buffers in SRAM1-SRAM3, the only difference I can think of now is that I had static uninitialized integer arrays in SRAM1-SRAM3 that didn't cause issues while static uninitialized structs in AXISRAM did cause the issue...Here's how I changed the linker script section to fix the issue, I changed the other sections to match:Code:.axisram (NOLOAD) :{. = ALIGN(4);_startAXISRAM = .;*(.axisram);*(.axisram*);. = ALIGN(4);_endAXISRAM = .;} >AXISRAMThanks for the quick help everyone!