0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何開發(fā)與存儲位置無關的STM32應用?

jf_pJlTbmA9 ? 來源:STM32單片機 ? 作者:STM32單片機 ? 2023-10-18 16:46 ? 次閱讀

1、前言

最近有客戶詢問,能否使用 STM32CubeIDE 在編譯時通過設置某個編譯選項,讓STM32 應用與存儲位置無關。這樣的優(yōu)勢是能使同一個固件被燒在 STM32 Flash 里的不同位置, 而在系統(tǒng) Bootloader 里只需要跳到相應的位置就可以正常執(zhí)行固件代碼。客戶希望STM32 代碼從 Flash 里執(zhí)行,不復制到 RAM 里;客戶希望是一個完整的映像,而不僅僅是其中某個函數(shù)做到了位置無關。

2、分析

嵌入式場景下,不一定有操作系統(tǒng)。即使有操作系統(tǒng),一般也是 RTOS。一般 RTOS沒有一個通用的程序加載器。因此,存儲位置無關的需求,在這時可以說無關緊要。但是,如果客戶需要進行在線固件更新,例如 IoT 應用的固件升級,那么位置無關就存在價值了。位置無關之后,對于不同的軟件版本,不需要頻繁的為燒寫位置的反復改變而修改編譯鏈接腳本。也不需要在代碼里顯式的在兩個 Bank 之間進行切換。

最簡單的情況是所有的代碼都復制到內存執(zhí)行。因為 Flash 的功能只是進行存儲,自然對 Flash 的位置沒有任何要求。但大部分 MCU 用戶面臨的真實案例都是 Flash 比較大,例如 ,1M 字節(jié) ;RAM 比較小,例如,128K 字節(jié)。在這種情況下,代碼在 Flash 原地執(zhí)行就是一個必須的選擇。Flash 位置改變,會影響從 Bootloader 跳轉之后的固件執(zhí)行時的 PC 指針,也就是 PC指針值會發(fā)生相應的變化。位置無關的原理,是讓應用程序經(jīng)過編譯后所生成的映像,其中的代碼和數(shù)據(jù),都是基于相對代碼的位置進行引用。那么,當應用被搬到不同位置時,他們的相對位置不變,從而執(zhí)行不受影響。

代碼和數(shù)據(jù)基于絕對地址還是基于相對地址,是由編譯器所決定。以客戶要求的

STM32CubeIDE 編譯工具為例,我們可以看到在[Project]->[Properties]->[C/C++ Build]->[Settings]->[Tool Settings]->[MCU GCC Compiler]->[Miscellaneous]已經(jīng)有一項[Position Independent Code (-fPIC)]。

是否只要選一下-fPIC 選項就大功告成了呢?答案是沒有那么簡單。

wKgaomUD3lSADVZtAAHhWs-yoBo379.png

事實上,對于完整應用程序工程,用戶應該經(jīng)過這些步驟將其變成位置無關:? 選擇正確的編譯器選項

? 去掉或者替換掉那些包含絕對位置的庫文件

? 修改代碼中的 Flash 絕對地址(這里以 STM32H7 的 CRC_Example 例程為例,其他情況下有可能要修改更多)

? 在 startup_xxx.s 匯編代碼里的 sidata

? 在 system_xxx.c 里的 SCB->VTOR 以及中斷向量表內容

? GOT

對于完整工程,要正確的跳轉到應用程序進行執(zhí)行,還需要由 Bootloader 向應用程序提供或者由應用程序在鏈接時自身解析計算,得到以下信息

? Flash 偏移量

? 中斷向量表的開始以及結束地址

? GOT 的開始以及結束地址

我們接下來就舉例說明這些步驟。

3、步驟

3.1. 選擇正確的編譯器選項

如果我們不使用任何編譯選項,編出來的代碼會怎么樣?我們可以通過.list 文件進行查看。.list 文件在 STM32 例程中默認生成,如果沒有請勾選如下選項, 在 [Project]->[Properties]->[C/C++ Build]->[Settings]->[Tool Settings]->[MCU Post Build outputs]->[Generate list file],可參考下圖。

wKgaomUD3lWAWakXAAKEtQtlrCQ438.png

wKgZomUD3laACDnvAAGBeiK3aVk710.png

wKgaomUD3lqAXWz1AAKAs0Qsbt0691.png

我們看到代碼中直接使用了變量的絕對地址,例如 0x2000 0028。我們不要被 literal pool 文字池的使用所迷惑,那個基于 PC 的操作只是為了取變量的絕對地址,例如, 0x2000 0028,并沒有將絕對地址變成相對地址。

當然大家說這里是 RAM 地址,沒有關系。我們選擇這個函數(shù)來說明,是因為位置無關的編譯器選項是不區(qū)分 RAM 還是 Flash 里的變量,而這個函數(shù)最簡單容易理解。如果我們查看另外一個復雜一點的函數(shù),例如,HAL_RCC_ClockConfig,我們可以看到以下對Flash 里變量的直接使用。這就不妙了,因為一旦改變了 Flash 下載的位置,在絕對地址處就取不出變量的真實內容了。

wKgZomUD3luAAlWbAAD3u1Xo1rk587.png

我們沒有辦法一個一個查找修改所有的變量。當然這里的變量是指全局變量。如果要修改,我們希望編譯器能把他們集中在一起。對于此,編譯器提供了多個編譯選項。例如,PIC 是位置無關代碼, PIE 是位置無關執(zhí)行。PIC 和 PIE 這兩者類似,但是存在一個顯著的差異是 PIE 會對部分全局變量優(yōu)化。我們可以觀察到用兩種不同編譯選項的效果。

wKgZomUD3l2AV-3wAAIVOBzXdrI619.png

其中 80004C0 地址處包含的是 GOT 自身的偏移量,存在 r2 里,要在兩次取全局變量 uwTickFreq 和 uwTick 時引用。GCC 編譯器引入 GOT 全局偏移量表來解決全局變量的絕對地址的問題。在之前對絕對地址的直接使用,現(xiàn)在被轉化成先取得 GOT 入口相對于 PC 的偏移,再獲得實際變量相對于 GOT 入口的偏移,從而得到實際變量的地址。計算公式如下:

實際變量的絕對地址=PC + GOT 相對于 PC 的偏移 + 變量地址相對于 GOT 的偏移

GOT 只有一個,如果代碼放在不同的位置,代碼自身就可以根據(jù) Bootloader 傳遞過來的信息,或者自行計算來對 GOT 進行更新。這樣變量的地址就和新的 Flash 偏移相匹配。

wKgaomUD3l6ASaVqAAGsxHxWOhE588.png

這里可以看到 80004c0 對應的 uwTick(可以從 str 指令結合 C 語言源代碼快速知道它對應于 uwTick)不再使用 GOT 偏移,而是相對于 PC 的偏移(與前文相比,多了一條指令 “add r3,pc”)。換句話說,PIE 對局部的全局變量做了優(yōu)化。這個優(yōu)化顯然不是我們所需要的。因為如此以來,RAM 變量的地址就會隨著 PC 的不同而不同。而我們則希望所有對RAM 的用法不發(fā)生變化。

為了能夠修改 GOT 內容,我們選擇將 GOT 最終存放在 RAM 中,導致代碼中對 GOT的尋址也是使用了相對于 PC 的偏移。而因為 RAM 有限,或者因為沒有虛擬內存的緣故,我們不希望 RAM 的用法有所不同,否則,可能代價很大。這時,一旦 Flash 代碼位置發(fā)生變化引起 PC 指針變化,GOT 就無法找到。因此,即使我們不使用 PIE,PIC 也沒有辦法單獨使用。為了確保沒有任何存放在 RAM 里的變量的位置是相對于 PC 的偏移。我們應該使用如下所有編譯選項,single-pic-base 讓系統(tǒng)只使用一個 PIC 基址,就是下文反匯編中看到r9;no-pic-data-is-text-relative 則讓編譯器不要讓任何變量相對于 PC 尋址。

wKgZomUD3l-AcZmMAABNbCX6skw395.jpg

wKgaomUD3mGAJas8AAJQ-dqeDu0708.png

這樣實際變量的絕對地址,就變成實際變量的絕對地址=PIC 基址 + GOT 相對于 PIC 基址的偏移 + 變量地址相對于 GOT的偏移使用以上編譯選項,這樣我們看到 HAL_IncTick 就如下所示:

wKgZomUD3mKANupCAAJjO9rL3s0694.png

這樣所有在 RAM 里的全局變量都是相對于 GOT 的偏移。注意,這個時候你編譯出來的代碼現(xiàn)在沒有辦法進行測試,盡管你只是改了編譯選項。這是因為 PIC 的基址需要你通過寄存器 r9 顯式指定。在本例中,我們在鏈接腳本里如下定義 GOT 的位置:

wKgaomUD3mOAaHmpAADFAKrZWr4610.png

因此,我們可以很容易的從.map 文件中獲得 GOT_START 的 RAM 地址,0x2000 0000,它就是 PIC 的基址。如果想測試編譯器選項是否如我們所期望,我們可以在Reset_Handler 開始部分加上如下語句(參考后文內存布局的代碼):

wKgaomUD3miAJtIpAAAGgchzc-E288.jpg

經(jīng)過測試,我們可以確信,編譯器選項的改動對我們最終執(zhí)行結果沒有影響。

值得注意的是,STM32 用戶的代碼,例如 RTOS 的移植, 也可能使用寄存器 r9。在這種情況,用戶應當解決沖突。一般情況寄存器 r9 對應用程序并不是必要的。

3.2. 去掉或者替換掉那些包含絕對位置的庫文件

我們要將位置無關的庫去掉或者替換掉。在 STM32 參考代碼里,我們需要

startup_xxx.s 里 C 庫調用去掉。示例如下:

wKgZomUD3mmAeTqiAAAkL5LfISg206.png

3.3. 修改 Flash 絕對地址

3.3.1. 內存布局

如果要對代碼中的 Flash 絕對地址進行修改,我們需要知道存放 Flash 絕對地址的 RAM起始和結束地址,以及需要增加或減少的 Flash 偏移量。存放 Flash 絕對地址的 RAM 起始和結束地址,在編譯時可以讓應用代碼本身借助自身鏈接腳本在鏈接時導出的變量得到,然后由應用程序在運行時存放在 RAM 中的固定位置;也可以在編譯后從.map 文件或使用工具解析 elf 文件獲得,然后作為應用程序一部分的元信息,例如,給應用程序加個頭部存放元信息,由 Bootloader 下載并解析,將其放入到 RAM 固定位置。

我們規(guī)劃在一段 RAM 里按如下順序存放如下元信息,它可以是應用程序本身在最初階段自我存放在這里,也可以簡單的由 Bootloader 解析元信息后,跳轉到應用程序之前就存放在這里。

wKgZomUD3muAZJo4AABGEtGo0Lc439.png

我們在前文已經(jīng)在鏈接腳本中定義了 GOT_START 和 GOT_END,我們還需要在鏈接腳本中定義 VT_START 和 VT_END。如下圖所示:

wKgaomUD3myAdpvZAAAg2hsiboM270.jpg

如果我們希望 Bootloader 僅僅是做簡單的跳轉,我們可以將規(guī)劃這段內存的工作,交給應用程序的初始化部分(在 “l(fā)dr sp, =_estack”之前)。假定 0x0 處對應為 0x2400 0000,參考代碼如下:

wKgZomUD3m6AWAK7AAKiHtOdcJw677.png

3.3.2. 匯編代碼

3.3.2.1. _sidata

在默認的 STM32 工程中,還有一些對變量絕對地址的使用。在 startup_xxx.s 有許多地方使用絕對地址,它們不能被編譯器收集到 GOT 中。其中,默認在鏈接腳本里的_sidata,標志 flash 里 RAM 數(shù)據(jù)區(qū)的 Flash 位置,需要修改。

wKgaomUD3nCATtZ7AALWnRds6WE206.png

注意,變量絕對地址本身不是個問題,而對它解應用,取它的內容才會發(fā)生錯誤。而這里的 _sidata 是要被初始化代碼使用,目的是將 Flash 的內容搬移到 RAM 里。我們顯然要對_sidata 進行修改,否則無法取得正確的內容到 RAM 里。

根據(jù)前文的內存布局,我們可以把 Flash 的偏移量從內存中放置在寄存器 r8 里,例如:

wKgZomUD3nGAHLD8AAAXuYIqLds005.png

則我們只需要一行簡單的代碼 “add r3,r8” 就可以修正_sidata 的地址。

wKgaomUD3nKAbBoXAABP5WZRdYA366.png

3.3.3. C 代碼

3.3.3.1. 公共函數(shù)

如果一段內存的數(shù)據(jù)都是硬編碼,我們只需要一個公共函數(shù)就可以對其循環(huán)進行修正。我們需要知道什么樣的地址之外不是 Flash 地址,那么就對這樣的值不做修改。例如,我們定義 0x1fff ffff 之外的就不是 Falsh 地址,相應的宏定義如下:

wKgZomUD3nWAUmcjAAD3Ze_GJ-E268.png

3.3.3.2. SCB->VTOR

在 C 語言中如果使用賦值語句進行硬編碼,編譯器也無法進行收集。例如在

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

wKgaomUD3neANBzbAAFwARvWczI524.png

中斷向量表相關的內容需要修改,包括兩部分:

? 中斷向量表的內存位置

? 中斷向量表的內容

我們應該將中斷向量表復制到 RAM 里,通過 UpdateOffset 函數(shù)修正其中包含的所有Flash 絕對地址的值,同時通過對 SCB->VTOR 賦值來將中斷向量表的位置指向我們修改過內容的 RAM 地址。注意,VTOR 所指向的地址 VT_RAM_START 要按照 ARM 要求,根據(jù)中斷總大小向上進行 2 的冪次對齊,例如,37 個字大小要使用 64 個字對齊。另外,中斷向量表的內容,也包含有 RAM 地址,對此,我們并不需要修改。當然,UpdateOffset 函數(shù)已

經(jīng)考慮到這一點,所以我們可以直接使用它。更新中斷向量表以及 VTOR 的參考代碼如下:

wKgZomUD3niASpV2AAISQHDL-XM141.png

3.3.3.3. GOT

編譯器已經(jīng)將 C 語言中所有全局變量的地址都收集到 GOT 中,因此我們很容易對其Flash 地址的內容進行修正,參考代碼如下:

wKgaomUD3nqAdnHjAAEhMu-UQ4M197.png

4、總結

除非你僅僅是運行一小塊代碼,否則開發(fā)位置無關的 STM32 完整工程,不僅僅要設置正確的編譯器選項,還要保證它所鏈接的預編譯的庫不含有絕對地址引用,要保證所有源代碼里沒有對絕對地址的硬編碼,包括修改 data 區(qū)的 Flash 起始地址,中斷向量表的內容與位置,以及 GOT 的內容。

來源:STM32單片機
免責聲明:本文為轉載文章,轉載此文目的在于傳遞更多信息,版權歸原作者所有。本文所用視頻、圖片、文字如涉及作品版權問題,請聯(lián)系小編進行處理

審核編輯 黃宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 存儲
    +關注

    關注

    13

    文章

    4314

    瀏覽量

    85854
  • RAM
    RAM
    +關注

    關注

    8

    文章

    1368

    瀏覽量

    114705
  • STM32
    +關注

    關注

    2270

    文章

    10900

    瀏覽量

    356091
收藏 人收藏

    評論

    相關推薦

    STM32應用與存儲位置無關

    最近有客戶詢問,能否使用 STM32CubeIDE 在編譯時通過設置某個編譯選項,讓STM32 應用與存儲位置無關。這樣的優(yōu)勢是能使同一個固件被燒在
    發(fā)表于 09-05 11:43 ?810次閱讀

    win10 IE瀏覽器無法更改臨時文件夾存儲位置

    臨時文件修改不了存儲位置的問題。這是怎么回事呢?接下來,小編就給大家介紹下win10系統(tǒng)下無法更改ie臨時文件位置的解決方案。具體方法如下: 1、每次用ie修改,注銷后還是沒有改變。2、可以通過修改注冊表
    發(fā)表于 03-08 13:46

    Keil C51 使用C語言編寫程序,怎么設置程序的起始存儲位置從0x1000開始

    使用STC 的IAP系列單片機,打算自己寫一段更新程序。求教C語言編寫的話要如何設置程序的起始存儲位置,該段程序準備存儲在以0x1000起始的連續(xù)的地址上。匯編中使用ORG命令就可以了,用C語言編寫的話沒有頭緒了,求助。
    發(fā)表于 02-09 17:27

    位置無關的代碼

    ) }--------------------------------------------這里把代碼段等鏈接到外存地址,那么確實在引導代碼里要注意“位置無關的代碼”問題,個人查了一些資料,只是提到bl,adr等相對pc的一些指令用法 .rodata ALIGN(4
    發(fā)表于 06-17 05:45

    【求助】指定變量在各個片上存儲區(qū)域的存儲位置時出現(xiàn)...

    和L2中,解算結果就是正確的。 請問這種情況是因為在cmd文件中已經(jīng)指定了已初始化/未初始化全局變量的存儲位置在DDR2中導致的嗎?或者是因為多變量跨存儲區(qū)域讀寫導致出現(xiàn)不可控的問題?如果不是,可能是哪里的問題? 請工程師撥冗解答,謝謝!
    發(fā)表于 05-13 06:40

    C語言中局部變量的存儲位置是如何分配的?

    ADS下C語言中局部變量的存儲位置是如何分配的?
    發(fā)表于 04-26 06:31

    請問存儲位置的內容如何固話到emmc中呢?

    接下來我該將該存儲位置的內容如何固話到emmc中呢?應該會有個mmc write的操作,但是我在文檔中沒找到,所以不知道是要寫到那個地址下,看到uboot env環(huán)境參數(shù)中有一段操作
    發(fā)表于 01-11 07:52

    存儲位元與存儲單元是什么含義

    存儲位元與存儲單元是什么含義?數(shù)據(jù)通信的方式可以分為哪幾種呢?
    發(fā)表于 01-21 07:17

    STM32+cubeMX點亮LED的方法

    STM32+cubeMX第一個工程,點亮LED打開cubeMX選擇new project創(chuàng)建一個stm32工程,芯片選擇stm32f103c8t6設置HSE時鐘來源為外部晶振設置系統(tǒng)Debug接口為串口設置時鐘樹設置工程名,
    發(fā)表于 01-27 08:26

    ARM的位置無關程序設計在Bootloader中的應用

    ARM的位置無關程序設計在Bootloader中的應用 ARM處理器支持位置無關的程序設計,這種程序加載到存儲器的任意地址空間都可以正常運
    發(fā)表于 03-29 15:12 ?1253次閱讀

    ARM處理器的位置無關程序設計

    ARM處理器支持位置無關的程序設計,這種程序加載到存儲器的任意地址空間都可以正常運行,其設計方法在嵌入式應用系統(tǒng)開發(fā)中具有重要的作用。尤其在裸機狀態(tài)下
    發(fā)表于 09-22 17:03 ?1041次閱讀

    ARM處理器位置無關的程序設計方案解析

    ARM處理器支持位置無關的程序設計,這種程序加載到存儲器的任意地址空間都可以正常運行,其設計方法在嵌入式應用系統(tǒng)開發(fā)中具有重要的作用。尤其在裸機狀態(tài)下
    發(fā)表于 10-27 13:00 ?4次下載

    ARM處理器的位置無關程序設計

    ARM處理器支持位置無關的程序設計,這種程序加載到存儲器的任意地址空間都可以正常運行,其設計方法在嵌入式應用系統(tǒng)開發(fā)中具有重要的作用。尤其在裸機狀態(tài)下
    發(fā)表于 12-01 01:16 ?577次閱讀

    STM32 內存分配解析及變量的存儲位置

    單元的。因此在一些嵌入式系統(tǒng)中,比如常用的STM32來講,內存映射被劃分為閃存段(也被稱為Flash,用于存儲代碼和只讀數(shù)據(jù))和RAM段,用于存儲讀寫數(shù)據(jù)。STM32 的 Flash
    發(fā)表于 11-26 18:51 ?49次下載
    <b class='flag-5'>STM32</b> 內存分配解析及變量的<b class='flag-5'>存儲位置</b>

    如何開發(fā)位置無關STM32 完整工程

    最近有客戶詢問,能否使用 STM32CubeIDE 在編譯時通過設置某個編譯選項,讓STM32 應用與存儲位置無關。
    的頭像 發(fā)表于 09-15 09:59 ?1648次閱讀