yo3hcv
yo3hcv

Reputation: 1669

STM32, GNU linker, inconsistent generated segment (program header) address with actual executable code address

I am trying to generate executable code located at different address than default 0x8000000 for bootloading purposes. Using latest STM32CubeIDE 1.11.2, but I guess that's a toolchain issue.

From linker script, I simply put my new origin to 0x8003800

/* Memories definition */
MEMORY
{
  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 144K
  FLASH    (rx)    : ORIGIN = 0x8003800,   LENGTH = 512K
}

Compile just fine, here is the GCC version

.\arm-none-eabi-gcc.exe -v
Using built-in specs.
COLLECT_GCC=W:\kit\prog\ST\toolchain\1.11.2\gcc\bin\arm-none-eabi-gcc.exe
COLLECT_LTO_WRAPPER=w:/kit/prog/st/toolchain/1.11.2/gcc/bin/../lib/gcc/arm-none-eabi/10.3.1/lto-wrapper.exe
Target: arm-none-eabi
Configured with: /build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/src/gcc/configure --build=x86_64-linux-gnu --host=x86_64-w64-mingw32 --target=arm-none-eabi --prefix=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/install-mingw --libexecdir=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/install-mingw/lib --infodir=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/install-mingw/share/doc/gcc-arm-none-eabi/info --mandir=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/install-mingw/share/doc/gcc-arm-none-eabi/man --htmldir=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/install-mingw/share/doc/gcc-arm-none-eabi/html --pdfdir=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/install-mingw/share/doc/gcc-arm-none-eabi/pdf --enable-languages=c,c++ --enable-mingw-wildcard --disable-decimal-float --disable-libffi --disable-libgomp --disable-libmudflap --disable-libquadmath --disable-libssp --disable-libstdcxx-pch --disable-nls --disable-shared --disable-threads --disable-tls --with-gnu-as --with-gnu-ld --with-headers=yes --with-newlib --with-python-dir=share/gcc-arm-none-eabi --with-sysroot=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/install-mingw/arm-none-eabi --with-libiconv-prefix=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/build-mingw/host-libs/usr --with-gmp=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/build-mingw/host-libs/usr --with-mpfr=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/build-mingw/host-libs/usr --with-mpc=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/build-mingw/host-libs/usr --with-isl=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/build-mingw/host-libs/usr --with-libelf=/build/gnu-tools-for-stm32_10.3-2021.10.20211105-1100/build-mingw/host-libs/usr --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,-Bdynamic -lm' --with-pkgversion='GNU Tools for STM32 10.3-2021.10.20211105-1100' --with-multilib-list=rmprofile,aprofile
Thread model: single
Supported LTO compression algorithms: zlib
gcc version 10.3.1 20210824 (release) (GNU Tools for STM32 10.3-2021.10.20211105-1100)

Generate a full listing

arm-none-eabi-objdump.exe -DCgxwtz myprog.elf > myprog.elf.asm

Now everything is fine, except that segment (program header) is located at 0x8000000 instead of 0x8003800. All the symbols are at correct addresses, executable code as well, just the program header is wrong (isn't it?)

Why I suspect is wrong?

Because P_OFFSET = 0 (offset in ELF file), P_ADDR = 0x8000000 and P_FILESZ = 0x000039c4 (size in ELF).
Meaning that any programmer will try to flash into STM32 micro at address 0x8000000, loading from ELF's file offset 0 which is completely wrong (there is ELF header).

The actual intended program from 0x8003800 will be flashed too, but until this address, will be flashed junk from ELF file.

enter image description here

Now my questions are

Do I do something wrong above?
How to generate a image located at other address that can be flashed without overwriting existing other images (like bootloader). Using pure binary content?

Thanks for help/clarifications.

Upvotes: 3

Views: 520

Answers (1)

yo3hcv
yo3hcv

Reputation: 1669

Thanks for voting my question :) and sorry to reply my own.
But StackOverflow is the most professionally community I know and I think we should benefit all from what we found.

After working entire weekend on this, I found & fix the problem and I think ST should improve their linker scripts a bit :))

1. So basically PHDRS (program headers or segments) are missing completely from ST scripts. They are not required from executable point of view (that runs on MCU) but from programmer point of view (if ELF file is used as source).

If PHDRS are not specified, linker will pick 0x8000000 as default which is ST's hardware reset vector, probably hard coded in their toolchain.

Should be like this:

    /* Memories definition */
MEMORY
{
  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 144K
  FLASH    (rx)   : ORIGIN = 0x8003800,   LENGTH = 524288 - 0x3800 
}

PHDRS
{
    flash_phdr PT_LOAD;
    ram_phdr PT_LOAD;
    null_phdr PT_NULL; /* for .bss and .heap */
}

That's things said, all sections assigned to FLASH, should also be explicitly declared in subsequent PHDR, so linker will correctly create one segment, at correct LMA address from all defined sections.

/* Sections */
SECTIONS
{

      /* The startup code into "FLASH" Rom type memory */
      .isr_vector :
      {
        . = ALIGN(4);
        KEEP(*(.isr_vector)) /* Startup code */
        . = ALIGN(4);
      } >FLASH : flash_phdr
    
      /* The program code and other data into "FLASH" Rom type memory */
      .text :
      {
        . = ALIGN(4);
        *(.text)           /* .text sections (code) */
        *(.text*)          /* .text* sections (code) */
        *(.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 */
      } >FLASH : flash_phdr

Take care for .data section, this belongs to RAM segment but it's contents (value of variables that needs initialization, other than zero) will also have to stay somewhere in FLASH, declare like this:

  /* Initialized data sections into "RAM" Ram type memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */

  } >RAM AT>FLASH : ram_phdr    /* it's content in RAM (belongs to RAM program header), but storage in FLASH */

2. Virtual sections like .bss of .stack shall belong to NULL PHDR
Somebody to format this code properly !!!

These sections are "virtual" in sense of they don't occupies any FLASH size. They just declare some design time variables, which may be used in C/ASM code, in order to

a) .bss -> zero all "not initialized C variables" at runtime. Use these variables for loops that effectively zero that piece of RAM.

b) .stack_heap -> use these variables to check (also at runtime) size of stack, heap, etc.

If not explicitly declared in NULL PHDR, linker will think these are real and will assign some LMA address that further programmer will be fooled.

/* Uninitialized data section into "RAM" Ram type memory / . = ALIGN(4); .bss : { _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;

} >RAM : null_phdr

/* User_heap_stack section, used to check that there is enough "RAM" Ram type memory left */ ._user_heap_stack : { . = ALIGN(8); PROVIDE ( end = . ); PROVIDE ( _end = . ); . = . + _Min_Heap_Size; . = . + _Min_Stack_Size; . = ALIGN(8);

} >RAM : null_phdr

After all these fixes, my nice output looks like this

enter image description here

As you can see, my test app is at 0x8003800 and now the segment (PHDR) reflects that too! Moreover, all the LMA's are correct now, these are used by programmer to load from ELF to MCU.

Of course, to avoid that, one can export (objdump) the binary of bootloader and manually program to desired address, but why to not use the correct and elegant way ELF file was designed for?

3. Final observation: There are a lot of redundant alignments at end of all segments some, and other curious

a) alignemt for .isr_vector is not required, this is the first section and is already aligned. Moreover, ARM requires alignment to 256, not to 4 !!!

b) ._user_heap_stack is aligned to 8, but all their MCU's are 32 bits so alignment to 4 is sufficient.

c) don't use start & end alignments :)) either one or the other :))) except of course guarding some design time variables.

Anyhow, these are minor ones.

So now, flashing the APP will not overwrite your bootloader anymore in ST micros :))))

Cheers!

Upvotes: 0

Related Questions