了解STM32F103C8T6的链接描述文件

时间:2020-02-04 17:14:19

标签: linker firmware bare-metal linker-scripts

我最近接触到STM32F103C8T6裸机编程,并且链接描述文件的实现似乎有些混乱。我在网上找到了两个版本的链接描述文件,尽管它们的内容差异很大,但令人惊讶的是两个文件都能按预期工作。

版本1 ,如here

SECTIONS
{
    .  = 0x0;         /* From 0x00000000 */
    .text : 
    {
        *(isr_vector) /* Interrupt Service Routine Vector table */
        *(.text)      /* Program code */
    }
}

,结果固件将用flash write_bank 0 add.bin 0刷新

版本2 ,如here

MEMORY {
    FLASH (rw): ORIGIN = 0x8000000, LENGTH = 64K
    RAM (rwx): ORIGIN = 0x20000000, LENGTH = 20K
}

ENTRY(Reset_Handler)
SECTIONS
{   .text : {
        KEEP(* (.isr_vector))
        * (.text*)
    } > FLASH

    __StackTop = ORIGIN(RAM) + LENGTH(RAM);
}

,结果固件将用flash write_image erase main.bin 0x8000000刷新

如您所见,在链接程序脚本和OpenOCD命令中均用于刷新固件,版本1闪烁.text0x00000000,而版本2闪烁至0x8000000。首先,我不确定这些地址是指什么:它们是LMA还是VMA?其次,为什么闪烁到不同的地址会产生相同的效果?

我做了一些研究,但是Programming manual似乎并没有解决我的困惑。

1 个答案:

答案 0 :(得分:2)

处理器启动以在武器地址空间中的地址0x00000000处寻找向量表。意法半导体已实现了自己的职责,以便应用程序闪存位于Arm地址空间中的地址0x08000000。

根据ST的引导模式,是否可以将内置的引导加载程序或应用程序镜像到地址0x00000000。这样,手臂访问地址0x00000000的操作将返回闪存中找到的值。如果镜像应用程序,则0x00000000和0x08000000都将从同一物理闪存设备读取值并返回它们。这里没有魔术,ARM总线具有要读取的地址和数据量,逻辑具有掩码和匹配项以确定它是哪个地址空间,然后如果将0x00000000转换为一定数量的KB,那么如果trap是一种方法从一个闪存库读取,从另一个闪存库读取。写入也是如此。

在零件的参考手册中搜索BOOT0。

理想情况下,您希望链接为0x08000000(或对于某些部分,但不是全部,为0x00200000),以便向量表条目在0x00000004、0x00000008等处读取,并返回0x0800xxxx地址,这样,实际上仅从向量表中读取0x00000000,然后在应用程序地址空间中其余程序。您会在文档中看到具有大量Flash的部件的0x00000000地址空间不支持Flash的整体大小,因此,如果链接为0x00000000,则无法使用所有Flash

现在这些链接描述脚本很有趣,首先,如果您在皮质m的向量表中看到了这些链接,则

.word       _start + 1
.word       _nmi_handler + 1
.word       _hard_fault + 1

找到另一个例子。

.thumb

.section isr_vector
.word 0x20001000
.word one
.word two

.text
.thumb_func
one:
    b one
.thumb_func
two:
    b two
.thumb_func

第一个链接描述文件

Disassembly of section .text:

00000000 <one-0xc>:
   0:   20001000    andcs   r1, r0, r0
   4:   0000000d    andeq   r0, r0, sp
   8:   0000000f    andeq   r0, r0, pc

0000000c <one>:
   c:   e7fe        b.n c <one>

0000000e <two>:
   e:   e7fe        b.n e <two>

在这种情况下,当手臂读取向量表以在地址0x00000004处找到复位向量时,ST部分将在应用程序闪存的第二个字中返回值(认为地址为0x08000004)

在这种情况下,它将找到0000000d,这意味着从地址0x0000000c开始提取指令。

使用第二个链接描述文件

.thumb

.section .isr_vector
.globl _isr_vector
_isr_vector:
.word 0x20001000
.word Reset_Handler

.text
.globl Reset_Handler
.thumb_func
Reset_Handler:
    b .


Disassembly of section .text:

08000000 <_isr_vector>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000009    stmdaeq r0, {r0, r3}

08000008 <Reset_Handler>:
 8000008:   e7fe        b.n 8000008 <Reset_Handler>

在这种情况下,当手臂在0x00000004处寻找复位向量时,它将得到0x08000009,这意味着在地址0x08000008处获取第一条指令,该地址位于应用程序闪存部分的地址空间中,这是首选。

您会发现一些ST部件在0x00200000处有一个小窗口,可以更快地读取其中的一些闪存(这是ITCM与AXIM的比较好,请阅读cortex-m7文档)。

通过msp432,我认为应用程序闪存是0x01000000相同的镜像对象,只是一个不同的地址。

.thumb_func
.align  2
.global Reset_Handler
.type   Reset_Handler, %function
Reset_Handler:

在第二个示例中请注意,作者具有额外的代码,.thumb_func和.type都将该标签标记为一个函数(使用ORR用1设置地址的lsbit,因此您不必具有丑陋的向量表,请使用工具)

例如:

.thumb

.section .isr_vector
.globl _isr_vector
_isr_vector:
.word 0x20001000
.word Reset_Handler
.word Something_Else

.text
.globl Reset_Handler
.thumb_func
Reset_Handler:
    b .

.type Something_Else, %function
Something_Else:
    b .
    

Disassembly of section .text:

08000000 <_isr_vector>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   0800000d    stmdaeq r0, {r0, r2, r3}
 8000008:   0800000f    stmdaeq r0, {r0, r1, r2, r3}

0800000c <Reset_Handler>:
 800000c:   e7fe        b.n 800000c <Reset_Handler>

0800000e <Something_Else>:
 800000e:   e7fe        b.n 800000e <Something_Else>

都可行,您使用的是您的个人喜好

.thumb_func

简洁明了,不需要匹配标签,但是它与位置有关,下一个标签是标记为功能的标签。也没有.arm_func

.type labelname, %function

适用于手臂和拇指代码,可以说要多键入一些,并且必须匹配标签名称,但是有一个优点,因为您清楚地指出了要标识为功能地址的标签,并且习惯适用于手臂和拇指模式。

两位作者(还是同一个人两次?)都创作了不必要的作品。

考虑这些

so.s

.thumb
.word 0x20001000
.word one
.word two

.thumb_func
one:
    b .

.thumb_func
two:
    b .
    

下一个

add r1,r2,r3
add r2,r3,r4
add r3,r4,r5

so.ld

MEMORY
{
    xyz : ORIGIN = 0x08000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > xyz
}

arm-none-eabi-as so.s -o so.o
arm-none-eabi-as next.s -o next.o
arm-none-eabi-ld -T so.ld so.o next.o -o so.elf
arm-none-eabi-objdump -D so.elf

so.elf:     file format elf32-littlearm


Disassembly of section .text:

08000000 <one-0xc>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   0800000d    stmdaeq r0, {r0, r2, r3}
 8000008:   0800000f    stmdaeq r0, {r0, r1, r2, r3}

0800000c <one>:
 800000c:   e7fe        b.n 800000c <one>

0800000e <two>:
 800000e:   e7fe        b.n 800000e <two>
 8000010:   e0821003    add r1, r2, r3
 8000014:   e0832004    add r2, r3, r4
 8000018:   e0843005    add r3, r4, r5

那很好,但是如果你

arm-none-eabi-ld -T so.ld next.o so.o -o so.elf
arm-none-eabi-objdump -D so.elf

so.elf:     file format elf32-littlearm


Disassembly of section .text:

08000000 <one-0x18>:
 8000000:   e0821003    add r1, r2, r3
 8000004:   e0832004    add r2, r3, r4
 8000008:   e0843005    add r3, r4, r5
 800000c:   20001000    andcs   r1, r0, r0
 8000010:   08000019    stmdaeq r0, {r0, r3, r4}
 8000014:   0800001b    stmdaeq r0, {r0, r1, r3, r4}

08000018 <one>:
 8000018:   e7fe        b.n 8000018 <one>

0800001a <two>:
 800001a:   e7fe        b.n 800001a <two>

不好。

如果未在链接描述文件中调用,则链接的命令行位置确定文件中.text项的顺序。提供示例或制作项目时,您是否不应该拥有用于生成代码的Makefile或构建说明?您真的需要在链接描述文件中做额外的工作吗? YMMV。

还请注意,链接描述文件中的内存标签只是用于连接MEMORY和SECTIONS之间的点的标签。您可以在限制范围内使用任何想要的字符串,并且有一些例外。

我曾经使用(rw)东西,但是不得不重新架构我的问题,而在两个版本的binutils之间,我的链接器脚本越来越少了。 (在我看来,该示例中存在错误)如果链接器抱怨缺少节(.rodata),则只需添加它即可。

裸机的魅力在于您可以自由选择操作方式,除非您使用库,否则必须在它们的沙箱中玩游戏。通常,您会发现工程化的链接程序脚本和引导程序,以及试图涵盖所有可能的用例和功能的此类脚本。只需制作一个涵盖您的用例即可。

您可以编写代码,而无需将.bss设置为零或复制.data,或者可以选择支持.data但不支持.bss,或者可以通过相对简单的链接程序脚本和引导程序完全支持。您拥有此MCU上的空间,您真的需要将堆栈设计为链接程序脚本吗?您知道零件的大小是使堆叠从顶部下降。 (当然,如果您执行链接魔术师,只需要输入一次大小)即可。如果您出于某种原因要支持堆(为什么要在MCU上堆?不是一件好事),也可以将其添加到链接描述文件中。

重新获得裸机之美,只要它起作用,您就可以自由地做任何您想做的事情。我建议您超越这些人所做的事情,并更好地学习工具(主要是在汇编语言方面),以便您有更多选择方式。

为什么两者都起作用,是因为该部分将部分/全部0x08000000臂地址空间镜像到0x00000000。因此,在窗口中都可以进行链接。

您可以选择用什么来标记这些内容,我使用术语“ arm地址空间”,也使用术语“物理地址”,这些内核中很少(如果有的话)具有虚拟内存空间,因此这没有任何意义。来自ARM IP的ARM总线(ARM不生产芯片,而是生产ST等公司购买的内核,并采用自己的逻辑或其他人员的逻辑来生产芯片)将发出带有这些物理地址之一的读取信息,如何芯片的响应基于他们的设计方式。向量表条目位于已知地址,这意味着逻辑将使用已知地址来读取每个项目,并且芯片和编程器需要响应,如果存在可以更改地址空间的mmu,向量表仍会如果处理程序逻辑通过mmu,则必须位于这些物理地址之一。

因此,在进行多项选择测试后,我会说逻辑内存地址LMA。

接下来的一件事是openocd支持写入0x00000000。 openocd完全支持Flash写入是一个额外的好处,因为它是特定于芯片的,并且需要花费一些时间。由于这些部件的0x00000000的性质以及如何对其加电(带状引脚),您可能将其捆扎错了,和/或openocd实现有一个假设或定义总是将0x00000000变成0x08000000或类似的东西,可能放在我最近为您查看的.cfg文件中,但没有查找此详细信息。