我最近接触到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闪烁.text
至0x00000000
,而版本2闪烁至0x8000000
。首先,我不确定这些地址是指什么:它们是LMA还是VMA?其次,为什么闪烁到不同的地址会产生相同的效果?
我做了一些研究,但是Programming manual似乎并没有解决我的困惑。
答案 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文件中,但没有查找此详细信息。