Martin Borýsek
Martin Borýsek

Reputation: 91

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?

Upvotes: 3

Views: 2651

Answers (2)

MSalters
MSalters

Reputation: 179991

"For example, is it possible somehow switch 32bit STM32 MCU to 16bit mode"

There is a 16 bits mode, but you may not understand exactly what that implies. To google it, you'd search for "ARM Thumb". That might very well result in *bin files which are a multiple of 2 bytes.

However, "32 bits mode" and "16 bits mode" usually refer to pointer or register sizes, not instruction sizes E.g. the AMD x64 instruction set may be referred to as "64 bits mode", even though its instructions are variable-length. Some x64 instructions are just one byte.

Upvotes: 0

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

Upvotes: 6

Related Questions