工程师笔记|如何开发与存储位置无关的STM32应用

STM32单片机 2022-09-02 17:30

关键词:PIC, PIE,位置

1、前言

最近有客户询问,能否使用 STM32CubeIDE 在编译时通过设置某个编译选项,让STM32 应用与存储位置无关。这样的优势是能使同一个固件被烧在 STM32 Flash 里的不同位置, 而在系统 Bootloader 里只需要跳到相应的位置就可以正常执行固件代码。客户希望STM32 代码从 Flash 里执行,不复制到 RAM 里;客户希望是一个完整的映像,而不仅仅是其中某个函数做到了位置无关。



2、分析

在嵌入式场景下,不一定有操作系统。即使有操作系统,一般也是 RTOS。一般 RTOS没有一个通用的程序加载器。因此,存储位置无关的需求,在这时可以说无关紧要。但是,如果客户需要进行在线固件更新,例如 IoT 应用的固件升级,那么位置无关就存在价值了。位置无关之后,对于不同的软件版本,不需要频繁的为烧写位置的反复改变而修改编译链接脚本。也不需要在代码里显式的在两个 Bank 之间进行切换。


最简单的情况是所有的代码都复制到内存执行。因为 Flash 的功能只是进行存储,自然对 Flash 的位置没有任何要求。但大部分 MCU 用户面临的真实案例都是 Flash 比较大,例如 ,1M 字节 ;RAM 比较小,例如,128K 字节。在这种情况下,代码在 Flash 原地执行就是一个必须的选择。Flash 位置改变,会影响从 Bootloader 跳转之后的固件执行时的 PC 指针,也就是 PC指针值会发生相应的变化。位置无关的原理,是让应用程序经过编译后所生成的映像,其中的代码和数据,都是基于相对代码的位置进行引用。那么,当应用被搬到不同位置时,他们的相对位置不变,从而执行不受影响。


代码和数据基于绝对地址还是基于相对地址,是由编译器所决定。以客户要求的

STM32CubeIDE 编译工具为例,我们可以看到在[Project]->[Properties]->[C/C++ Build]->[Settings]->[Tool Settings]->[MCU GCC Compiler]->[Miscellaneous]已经有一项[Position Independent Code (-fPIC)]。


是否只要选一下-fPIC 选项就大功告成了呢?答案是没有那么简单。




事实上,对于完整应用程序工程,用户应该经过这些步骤将其变成位置无关:• 选择正确的编译器选项

• 去掉或者替换掉那些包含绝对位置的库文件

• 修改代码中的 Flash 绝对地址(这里以 STM32H7 的 CRC_Example 例程为例,

其他情况下有可能要修改更多) o 在 startup_xxx.s 汇编代码里的 sidata

    o 在 system_xxx.c 里的 SCB->VTOR 以及中断向量表内容

    o GOT

对于完整工程,要正确的跳转到应用程序进行执行,还需要由 Bootloader 向应用程序提供或者由应用程序在链接时自身解析计算,得到以下信息:

• Flash 偏移量

• 中断向量表的开始以及结束地址

• GOT 的开始以及结束地址


我们接下来就举例说明这些步骤。



3、步骤

3.1. 选择正确的编译器选项

如果我们不使用任何编译选项,编出来的代码会怎么样?我们可以通过.list 文件进行查看。.list 文件在 STM32 例程中默认生成,如果没有请勾选如下选项, 在 [Project]->[Properties]->[C/C++ Build]->[Settings]->[Tool Settings]->[MCU Post Build outputs]->[Generate list file],可参考下图。



我们看到代码中直接使用了变量的绝对地址,例如 0x2000 0028。我们不要被 literal pool 文字池的使用所迷惑,那个基于 PC 的操作只是为了取变量的绝对地址,例如, 0x2000 0028,并没有将绝对地址变成相对地址。

当然大家说这里是 RAM 地址,没有关系。我们选择这个函数来说明,是因为位置无关的编译器选项是不区分 RAM 还是 Flash 里的变量,而这个函数最简单容易理解。如果我们查看另外一个复杂一点的函数,例如,HAL_RCC_ClockConfig,我们可以看到以下对Flash 里变量的直接使用。这就不妙了,因为一旦改变了 Flash 下载的位置,在绝对地址处就取不出变量的真实内容了。


我们没有办法一个一个查找修改所有的变量。当然这里的变量是指全局变量。如果要修改,我们希望编译器能把他们集中在一起。对于此,编译器提供了多个编译选项。例如,PIC 是位置无关代码, PIE 是位置无关执行。PIC 和 PIE 这两者类似,但是存在一个显著的差异是 PIE 会对部分全局变量优化。我们可以观察到用两种不同编译选项的效果。


其中 80004C0 地址处包含的是 GOT 自身的偏移量,存在 r2 里,要在两次取全局变量 uwTickFreq 和 uwTick 时引用。GCC 编译器引入 GOT 全局偏移量表来解决全局变量的绝对地址的问题。在之前对绝对地址的直接使用,现在被转化成先取得 GOT 入口相对于 PC 的偏移,再获得实际变量相对于 GOT 入口的偏移,从而得到实际变量的地址。计算公式如下:


实际变量的绝对地址=PC + GOT 相对于 PC 的偏移 + 变量地址相对于 GOT 的偏移


GOT 只有一个,如果代码放在不同的位置,代码自身就可以根据 Bootloader 传递过来的信息,或者自行计算来对 GOT 进行更新。这样变量的地址就和新的 Flash 偏移相匹配。



这里可以看到 80004c0 对应的 uwTick(可以从 str 指令结合 C 语言源代码快速知道它对应于 uwTick)不再使用 GOT 偏移,而是相对于 PC 的偏移(与前文相比,多了一条指令 “add r3,pc”)。换句话说,PIE 对局部的全局变量做了优化。这个优化显然不是我们所需要的。因为如此以来,RAM 变量的地址就会随着 PC 的不同而不同。而我们则希望所有对RAM 的用法不发生变化。


为了能够修改 GOT 内容,我们选择将 GOT 最终存放在 RAM 中,导致代码中对 GOT的寻址也是使用了相对于 PC 的偏移。而因为 RAM 有限,或者因为没有虚拟内存的缘故,我们不希望 RAM 的用法有所不同,否则,可能代价很大。这时,一旦 Flash 代码位置发生变化引起 PC 指针变化,GOT 就无法找到。因此,即使我们不使用 PIE,PIC 也没有办法单独使用。为了确保没有任何存放在 RAM 里的变量的位置是相对于 PC 的偏移。我们应该使用如下所有编译选项,single-pic-base 让系统只使用一个 PIC 基址,就是下文反汇编中看到

r9;no-pic-data-is-text-relative 则让编译器不要让任何变量相对于 PC 寻址。



这样实际变量的绝对地址,就变成实际变量的绝对地址=PIC 基址 + GOT 相对于 PIC 基址的偏移 + 变量地址相对于 GOT的偏移使用以上编译选项,这样我们看到 HAL_IncTick 就如下所示:


这样所有在 RAM 里的全局变量都是相对于 GOT 的偏移。注意,这个时候你编译出来的代码现在没有办法进行测试,尽管你只是改了编译选项。这是因为 PIC 的基址需要你通过寄存器 r9 显式指定。在本例中,我们在链接脚本里如下定义 GOT 的位置:




因此,我们可以很容易的从.map 文件中获得 GOT_START 的 RAM 地址,0x2000 0000,它就是 PIC 的基址。如果想测试编译器选项是否如我们所期望,我们可以在Reset_Handler 开始部分加上如下语句(参考后文内存布局的代码): 

经过测试,我们可以确信,编译器选项的改动对我们最终执行结果没有影响。

值得注意的是,STM32 用户的代码,例如 RTOS 的移植, 也可能使用寄存器 r9。在这种情况,用户应当解决冲突。一般情况寄存器 r9 对应用程序并不是必要的。


3.2. 去掉或者替换掉那些包含绝对位置的库文件

我们要将位置无关的库去掉或者替换掉。在 STM32 参考代码里,我们需要

startup_xxx.s 里 C 库调用去掉。示例如下:


3.3. 修改 Flash 绝对地址

3.3.1. 内存布局

如果要对代码中的 Flash 绝对地址进行修改,我们需要知道存放 Flash 绝对地址的 RAM起始和结束地址,以及需要增加或减少的 Flash 偏移量。存放 Flash 绝对地址的 RAM 起始和结束地址,在编译时可以让应用代码本身借助自身链接脚本在链接时导出的变量得到,然后由应用程序在运行时存放在 RAM 中的固定位置;也可以在编译后从.map 文件或使用工具解析 elf 文件获得,然后作为应用程序一部分的元信息,例如,给应用程序加个头部存放元信息,由 Bootloader 下载并解析,将其放入到 RAM 固定位置。


我们规划在一段 RAM 里按如下顺序存放如下元信息,它可以是应用程序本身在最初阶段自我存放在这里,也可以简单的由 Bootloader 解析元信息后,跳转到应用程序之前就存放在这里。



我们在前文已经在链接脚本中定义了 GOT_START 和 GOT_END,我们还需要在链接脚本中定义 VT_START 和 VT_END。如下图所示:


如果我们希望 Bootloader 仅仅是做简单的跳转,我们可以将规划这段内存的工作,交给应用程序的初始化部分(在 “ldr sp, =_estack”之前)。假定 0x0 处对应为 0x2400 0000,参考代码如下:


3.3.2. 汇编代码

3.3.2.1. _sidata

在默认的 STM32 工程中,还有一些对变量绝对地址的使用。在 startup_xxx.s 有许多地方使用绝对地址,它们不能被编译器收集到 GOT 中。其中,默认在链接脚本里的_sidata,标志 flash 里 RAM 数据区的 Flash 位置,需要修改。



注意,变量绝对地址本身不是个问题,而对它解应用,取它的内容才会发生错误。而这里的 _sidata 是要被初始化代码使用,目的是将 Flash 的内容搬移到 RAM 里。我们显然要对_sidata 进行修改,否则无法取得正确的内容到 RAM 里。

根据前文的内存布局,我们可以把 Flash 的偏移量从内存中放置在寄存器 r8 里,例如:

 


则我们只需要一行简单的代码 “add r3,r8” 就可以修正_sidata 的地址。




3.3.3. C 代码

3.3.3.1. 公共函数

如果一段内存的数据都是硬编码,我们只需要一个公共函数就可以对其循环进行修正。我们需要知道什么样的地址之外不是 Flash 地址,那么就对这样的值不做修改。例如,我们定义 0x1fff ffff 之外的就不是 Falsh 地址,相应的宏定义如下:


3.3.3.2. SCB->VTOR

在 C 语言中如果使用赋值语句进行硬编码,编译器也无法进行收集。例如在

system_stm32xxxx.c 中的 SystemInit 有如下语句:


中断向量表相关的内容需要修改,包括两部分:

• 中断向量表的内存位置

• 中断向量表的内容

我们应该将中断向量表复制到 RAM 里,通过 UpdateOffset 函数修正其中包含的所有Flash 绝对地址的值,同时通过对 SCB->VTOR 赋值来将中断向量表的位置指向我们修改过内容的 RAM 地址。注意,VTOR 所指向的地址 VT_RAM_START 要按照 ARM 要求,根据中断总大小向上进行 2 的幂次对齐,例如,37 个字大小要使用 64 个字对齐。另外,中断向量表的内容,也包含有 RAM 地址,对此,我们并不需要修改。当然,UpdateOffset 函数已

经考虑到这一点,所以我们可以直接使用它。更新中断向量表以及 VTOR 的参考代码如下:


3.3.3.3. GOT

编译器已经将 C 语言中所有全局变量的地址都收集到 GOT 中,因此我们很容易对其Flash 地址的内容进行修正,参考代码如下:


4、总结

除非你仅仅是运行一小块代码,否则开发位置无关的 STM32 完整工程,不仅仅要设置正确的编译器选项,还要保证它所链接的预编译的库不含有绝对地址引用,要保证所有源代码里没有对绝对地址的硬编码,包括修改 data 区的 Flash 起始地址,中断向量表的内容与位置,以及 GOT 的内容。



                

© THE END



点击“阅读原文”,可下载原文档

STM32单片机 ST MCU (产品+工具+资料+技术+市场+活动)x 您的关注x您的支持 = STM32 单片机蝴蝶乐园
评论
  •   在信号处理过程中,由于信号的时域截断会导致频谱扩展泄露现象。那么导致频谱泄露发生的根本原因是什么?又该采取什么样的改善方法。本文以ADC性能指标的测试场景为例,探讨了对ADC的输出结果进行非周期截断所带来的影响及问题总结。 两个点   为了更好的分析或处理信号,实际应用时需要从频域而非时域的角度观察原信号。但物理意义上只能直接获取信号的时域信息,为了得到信号的频域信息需要利用傅里叶变换这个工具计算出原信号的频谱函数。但对于计算机来说实现这种计算需要面对两个问题: 1.
    TIAN301 2025-01-14 14:15 104浏览
  • 食物浪费已成为全球亟待解决的严峻挑战,并对环境和经济造成了重大影响。最新统计数据显示,全球高达三分之一的粮食在生产过程中损失或被无谓浪费,这不仅导致了资源消耗,还加剧了温室气体排放,并带来了巨大经济损失。全球领先的光学解决方案供应商艾迈斯欧司朗(SIX:AMS)近日宣布,艾迈斯欧司朗基于AS7341多光谱传感器开发的创新应用来解决食物浪费这一全球性难题。其多光谱传感解决方案为农业与食品行业带来深远变革,该技术通过精确判定最佳收获时机,提升质量控制水平,并在整个供应链中有效减少浪费。 在2024
    艾迈斯欧司朗 2025-01-14 18:45 49浏览
  • ARMv8-A是ARM公司为满足新需求而重新设计的一个架构,是近20年来ARM架构变动最大的一次。以下是对ARMv8-A的详细介绍: 1. 背景介绍    ARM公司最初并未涉足PC市场,其产品主要针对功耗敏感的移动设备。     随着技术的发展和市场需求的变化,ARM开始扩展到企业设备、服务器等领域,这要求其架构能够支持更大的内存和更复杂的计算任务。 2. 架构特点    ARMv8-A引入了Execution State(执行状
    丙丁先生 2025-01-12 10:30 465浏览
  • 随着数字化的不断推进,LED显示屏行业对4K、8K等超高清画质的需求日益提升。与此同时,Mini及Micro LED技术的日益成熟,推动了间距小于1.2 Pitch的Mini、Micro LED显示屏的快速发展。这类显示屏不仅画质卓越,而且尺寸适中,通常在110至1000英寸之间,非常适合应用于电影院、监控中心、大型会议、以及电影拍摄等多种室内场景。鉴于室内LED显示屏与用户距离较近,因此对于噪音控制、体积小型化、冗余备份能力及电气安全性的要求尤为严格。为满足这一市场需求,开关电源技术推出了专为
    晶台光耦 2025-01-13 10:42 495浏览
  • 新年伊始,又到了对去年做总结,对今年做展望的时刻 不知道你在2024年初立的Flag都实现了吗? 2025年对自己又有什么新的期待呢? 2024年注定是不平凡的一年, 一年里我测评了50余块开发板, 写出了很多科普文章, 从一个小小的工作室成长为科工公司。 展望2025年, 中国香河英茂科工, 会继续深耕于,具身机器人、飞行器、物联网等方面的研发, 我觉得,要向未来学习未来, 未来是什么? 是掌握在孩子们生活中的发现,和精历, 把最好的技术带给孩子,
    丙丁先生 2025-01-11 11:35 454浏览
  • 流量传感器是实现对燃气、废气、生活用水、污水、冷却液、石油等各种流体流量精准计量的关键手段。但随着工业自动化、数字化、智能化与低碳化进程的不断加速,采用传统机械式检测方式的流量传感器已不能满足当代流体计量行业对于测量精度、测量范围、使用寿命与维护成本等方面的精细需求。流量传感器的应用场景(部分)超声波流量传感器,是一种利用超声波技术测量流体流量的新型传感器,其主要通过发射超声波信号并接收反射回来的信号,根据超声波在流体中传播的时间、幅度或相位变化等参数,间接计算流体的流量,具有非侵入式测量、高精
    华普微HOPERF 2025-01-13 14:18 477浏览
  • 数字隔离芯片是现代电气工程师在进行电路设计时所必须考虑的一种电子元件,主要用于保护低压控制电路中敏感电子设备的稳定运行与操作人员的人身安全。其不仅能隔离两个或多个高低压回路之间的电气联系,还能防止漏电流、共模噪声与浪涌等干扰信号的传播,有效增强电路间信号传输的抗干扰能力,同时提升电子系统的电磁兼容性与通信稳定性。容耦隔离芯片的典型应用原理图值得一提的是,在电子电路中引入隔离措施会带来传输延迟、功耗增加、成本增加与尺寸增加等问题,而数字隔离芯片的目标就是尽可能消除这些不利影响,同时满足安全法规的要
    华普微HOPERF 2025-01-15 09:48 61浏览
  • PNT、GNSS、GPS均是卫星定位和导航相关领域中的常见缩写词,他们经常会被用到,且在很多情况下会被等同使用或替换使用。我们会把定位导航功能测试叫做PNT性能测试,也会叫做GNSS性能测试。我们会把定位导航终端叫做GNSS模块,也会叫做GPS模块。但是实际上他们之间是有一些重要的区别。伴随着技术发展与越发深入,我们有必要对这三个词汇做以清晰的区分。一、什么是GPS?GPS是Global Positioning System(全球定位系统)的缩写,它是美国建立的全球卫星定位导航系统,是GNSS概
    德思特测试测量 2025-01-13 15:42 487浏览
  • 01. 什么是过程能力分析?过程能力研究利用生产过程中初始一批产品的数据,预测制造过程是否能够稳定地生产符合规格的产品。可以把它想象成一种预测。通过历史数据的分析,推断未来是否可以依赖该工艺持续生产高质量产品。客户可能会要求将过程能力研究作为生产件批准程序 (PPAP) 的一部分。这是为了确保制造过程能够持续稳定地生产合格的产品。02. 基本概念在定义制造过程时,目标是确保生产的零件符合上下规格限 (USL 和 LSL)。过程能力衡量制造过程能多大程度上稳定地生产符合规格的产品。核心概念很简单:
    优思学院 2025-01-12 15:43 516浏览
  • 根据Global Info Research(环洋市场咨询)项目团队最新调研,预计2030年全球无人机电池和电源产值达到2834百万美元,2024-2030年期间年复合增长率CAGR为10.1%。 无人机电池是为无人机提供动力并使其飞行的关键。无人机使用的电池类型因无人机的大小和型号而异。一些常见的无人机电池类型包括锂聚合物(LiPo)电池、锂离子电池和镍氢(NiMH)电池。锂聚合物电池是最常用的无人机电池类型,因为其能量密度高、设计轻巧。这些电池以输出功率大、飞行时间长而著称。不过,它们需要
    GIRtina 2025-01-13 10:49 180浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦