Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ARM binary size

I'm programming MCUs like STM32F4** and STM32F0**, using ARM GCC noneabi compiler , c/c++ and found interesting pattern.

If I build some *.bin file, it's size is always divisible by 4.

I think it might be because MCU is 32bit (=4 byte). So bin_size%4==0. I have tried some "hacks"; for example enlarge some byte array by 1, but binary size is always the same. When I enlarge array more, binary size is bigger, but again divisible by 4.

Can I consider this effect as axiom?

Or is there some situation when this doesn't work? For example, is it possible somehow switch 32bit STM32 MCU to 16bit mode? Or is possible to create non-divisible binary by 4 with another compiler?

like image 606
Martin Borýsek Avatar asked May 16 '26 19:05

Martin Borýsek


1 Answers

It's possible to create binaries of arbitrary size, the 4 byte alignment is just a matter of convenience. Everyone does it this way, and everyone expects it.

The alignment is enforced in the linker script file. If you look in the *.ld file of your project, you'll find that there are a lot of

. = ALIGN(4);

statements. They instruct the linker to advance the current output address to a value that's divisible by 4.

So I've created an empty project, and deleted most of the ALIGN lines from the linker script:

ENTRY(Reset_Handler)
__stack = 0x20014000;
MEMORY
{
FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 512K
RAM   (xrw)     : ORIGIN = 0x20000000, LENGTH = 80K
}
SECTIONS
{
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector))
    . = ALIGN(4);
  } >FLASH
  .text :
  {
    *(.text)
    *(.text*)
  } >FLASH
  .rodata :
  {
    *(.rodata)
    *(.rodata*)
  } >FLASH
  _sidata = LOADADDR(.data);
  .data : 
  {
    _sdata = .;
    *(.data)
    *(.data*)
    _edata = .;
  } >RAM AT> FLASH
  .bss :
  {
    _sbss = .;
    *(.bss)
    *(.bss*)
    *(COMMON)
    _ebss = .;
  } >RAM
}

a minimal program:

void Reset_Handler(void) {
    while(1)
        ;
}

compiled and linked it with -nostartfiles -nodefaultlibs -nostdlib, to leave out all the standard library stuff. The result is

arm-none-eabi-size --format=berkeley "unaligned.elf"
   text    data     bss     dec     hex filename
    320       0       0     320     140 unaligned.elf

evenly divisible by four. Then I've added a char variable, and did something with it:

volatile char c = 0x42;
void Reset_Handler(void) {
    while(1)
        c+=1;
}

which resulted in

Invoking: Cross ARM GNU Print Size
arm-none-eabi-size --format=berkeley "unaligned.elf"
   text    data     bss     dec     hex filename
    336       1       0     337     151 unaligned.elf
Finished building: unaligned.siz

The instructions are aligned to 16 bits.

Cortex-M based MCUs like the STM32 series use the Thumb2 instruction set, which is a mix of 16 and 32 bit instructions. It turns out that our first program just happened to have a length divisible by four. I've added a single nop instruction

void Reset_Handler(void) {
    asm("nop");
    while(1)
        ;
}

and the size grew by two bytes, compared to the original:

Invoking: Cross ARM GNU Print Size
arm-none-eabi-size --format=berkeley "unaligned.elf"
   text    data     bss     dec     hex filename
    322       0       0     322     142 unaligned.elf
Finished building: unaligned.siz
like image 92
followed Monica to Codidact Avatar answered May 19 '26 07:05

followed Monica to Codidact



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!