STM32,GNU 链接器,在生成的程序头地址与实际可执行代码地址不一致。

3

我正在尝试生成可执行代码,其位于默认地址0x8000000之外,以用于引导目的。使用最新的STM32CubeIDE 1.11.2,但我猜这是一个工具链问题。

从链接脚本中,我只需将我的新起点放置在0x8003800处。

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

编译完全正常,这是GCC版本

.\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)

生成完整列表。
arm-none-eabi-objdump.exe -DCgxwtz myprog.elf > myprog.elf.asm

现在一切都很好,除了段(程序头)位于0x8000000而不是0x8003800。所有符号的地址都正确,可执行代码也正确,只有程序头是错误的(不是吗?)

为什么我怀疑是错的呢?

因为P_OFFSET = 0(ELF文件中的偏移量),P_ADDR = 0x8000000,P_FILESZ = 0x000039c4(ELF中的大小)。这意味着任何程序员都会尝试将其闪存到STM32微控制器的地址0x8000000,从ELF文件偏移量0加载,这是完全错误的(因为有ELF头)。

实际预期的程序将从0x8003800开始闪存,但在此地址之前,将从ELF文件中闪存垃圾。

enter image description here

现在我的问题是

我上面做错了什么吗?
如何生成一个位于其他地址的图像,可以在不覆盖现有其他图像(如引导加载程序)的情况下刷写。使用纯二进制内容?

感谢您的帮助/澄清。


1
有趣。我有一个类似的项目,我刚刚看到和你一样的情况。我的应用程序从0x08004000开始,但是该部分从0x08000000开始。不过它还是能工作的!在我的情况下,我生成一个.bin文件,然后将CRC插入其中,因此我从未加载.elf文件。.bin文件中没有存储任何偏移量,您只需要知道在哪里编程它(在我的情况下,在0x08004000处)。 - pmacfarlane
1
部分答案是问题在于每个段的对齐方式为2 ** 16(请参见您从第一个突出显示框中排除的右侧列)。 看起来起始地址已经向下舍入以满足此对齐要求,并且大小已增加相同的量。 这意味着一切仍将位于正确的地址,只是它认为段开始于该部分之前。 我不知道为什么它坚持认为段从16位对齐地址开始。 8086 addressing 的遗留问题? - Tom V
我刚刚查看了我的一些项目,它们的头部也有2 ** 16。也许这是elf格式的一个限制/特性? - Tom V
@pmacfarlane 我也在做CRC相关的东西 :))) 但我更喜欢操作ELF文件,查找符号,用一些C#计算CRC,然后保存回去。 - yo3hcv
1个回答

0

感谢您投票支持我的问题 :),很抱歉要回复自己。
但是StackOverflow是我所知道的最专业的社区,我认为我们应该从我们发现的东西中受益。

在整个周末工作后,我找到并解决了问题,我认为ST应该稍微改进一下他们的链接脚本:))

1. 基本上,ST脚本完全缺少PHDRS(程序头或段)。 从可执行文件的角度来看(在MCU上运行),它们不是必需的,但从程序员的角度来看(如果ELF文件用作源),则是必需的。

如果未指定PHDRS,则链接器将选择0x8000000作为默认值,这是ST的硬件复位向量,可能在他们的工具链中硬编码。

应该像这样:

    /* 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 */
}

话虽如此,所有分配给FLASH的部分也应在后续PHDR中明确声明,以便链接器可以从所有定义的部分正确创建一个段,并将其放置在正确的LMA地址。

/* 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

注意.data节段,它属于RAM段,但是其中的内容(需要初始化的变量值,除零之外)也必须存储在FLASH中,声明方式如下:

  /* 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.虚拟section,如.bss或.stack,应属于NULL PHDR
有人来正确格式化这段代码吗!!!

这些section是“虚拟”的,因为它们不占任何FLASH空间。它们只声明一些设计时变量,这些变量可以在C/ASM代码中使用,以便

a) .bss -> 在运行时将所有“未初始化的C变量”清零。 使用这些变量来循环有效地清零这块RAM。

b) .stack_heap -> 使用这些变量来检查(也在运行时)堆栈、堆等的大小。

如果没有明确声明为NULL PHDR,链接器会认为这些是真实的,并分配一些LMA地址,这会让后续的程序员误解。

/* 未初始化的数据区域进入“RAM”类型内存 / . = ALIGN(4); .bss : { _sbss = .; / 在bss开始处定义一个全局符号 */ 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, 用于检查是否有足够的“RAM”类型内存剩余 */ ._user_heap_stack : { . = ALIGN(8); PROVIDE ( end = . ); PROVIDE ( _end = . ); . = . + _Min_Heap_Size; . = . + _Min_Stack_Size; . = ALIGN(8);

} >RAM : null_phdr

所有这些修复后,我的输出结果如下所示

enter image description here

正如您所看到的,我的测试应用程序位于0x8003800,现在段(PHDR)也反映了这一点!此外,所有LMA现在都是正确的,这些由程序员用于从ELF加载到MCU。

当然,为了避免这种情况,可以导出(objdump)引导加载程序的二进制文件,并手动编程到所需地址,但为什么不使用ELF文件设计的正确而优雅的方式呢?

3.最终观察:所有段末尾都有很多冗余对齐和其他好奇的事情

a).isr_vector的对齐不是必需的,这是第一节,已经对齐。此外,ARM需要对齐到256,而不是4!!!

b)._user_heap_stack对齐到8,但他们所有的MCU都是32位的,所以对齐到4就足够了。

c)不要使用起始和结束对齐:)))任何一个或另一个:))除了保护一些设计时间变量之外。

无论如何,这些都是小问题。

因此,现在,在ST微控制器中刷写APP将不再覆盖您的引导加载程序:)))

干杯!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接