1。访问源代码中链接脚本变量的官方文档:
查看本页底部的示例:
https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html
因此,当您在源代码中使用链接描述文件定义的符号时,您应该始终获取符号的地址,并且永远不要尝试使用它的值。例如,假设您想将名为 .ROM 的内存段的内容复制到名为 .FLASH 的段中,并且链接描述文件包含以下声明:
start_of_ROM = .ROM;
end_of_ROM = .ROM + sizeof (.ROM);
start_of_FLASH = .FLASH;
那么执行复制的 C 源代码将是:
extern char start_of_ROM, end_of_ROM, start_of_FLASH;
memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);
注意“&”运算符的使用。这些都是正确的。或者,可以将符号视为向量或数组的名称,然后代码将再次按预期工作:
[==>这是我的首选方法
extern char start_of_ROM[], end_of_ROM[], start_of_FLASH[];
memcpy (start_of_FLASH, start_of_ROM, end_of_ROM - start_of_ROM);
注意如何使用此方法不需要使用“&”运算符。
2。您的具体情况:
所以,如果我想获取链接脚本变量 __START_OF_PROG_MEMORY 的值以用于我的 C 程序,我会这样做:
#include <stdint.h>
// linkerscript variable; NOT an array; `[]` is required to access a
// linkerscript variable like a normal variable--see here:
// https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html
extern uint32_t __START_OF_PROG_MEMORY[];
// Read and use the `__START_OF_PROG_MEMORY` linkerscript variable
uint32_t start_of_program = (uint32_t)__START_OF_PROG_MEMORY;
3。请注意,如果您为 STM32 微控制器执行此操作:
另一个获取程序存储器起始地址(通常是闪存——存储程序起始地址的地方)的技巧是简单地获取g_pfnVectors全局ISR向量表数组的地址,即在您的启动程序集文件中定义(例如:“startup_stm32f746xx.s”)。为此,请执行以下操作:
// true array (vector table of all ISRs), from the startup assembly .s file
extern uint32_t g_pfnVectors[];
// Get the starting address of where the application/program **is stored**
// **in flash memory**:
// (My preferred approach, as I find it more clear) Get the address of the
// first element of this array and cast it to a 4-byte unsigned integer
uint32_t application_start_address = (uint32_t)&g_pfnVectors[0];
// OR (same thing as the line just above, just in a different way)
uint32_t application_start_address = (uint32_t)g_pfnVectors;
瞧!太神奇了:)。
重要提示(有关 STM32 微控制器的更多详细信息):
-
存储在闪存中的应用程序/程序位置:我确实不是的意思是application_start_address 是程序开始运行的第一个字节运行 (这是初始程序计数器(PC)),也不我的意思是它是程序堆栈内存开始的第一个字节在RAM中(这是初始堆栈指针 (SP))。我的意思是它是程序被存储的闪存中的第一个字节。这里差别很大。为了管理 flash 内存中的两个应用程序,例如,对于 OTA(空中下载)更新,我说 application_start_address 是 flash 中的第一个位置存储程序的位置。
-
初始堆栈指针 (SP): 如果您正在寻找 RAM 中 堆栈内存开始 的第一个位置,则该地址位置存储在闪存中作为g_pfnVectors全局向量表中的第一个字(4字节)(同样,通常在闪存中内存) ,并且可以像这样获得或读取:
// Initial Stack Pointer (SP) value where the program stack begins.
uint32_t initial_stack_ptr_location_in_ram = g_pfnVectors[0];
请参阅编程手册PM0253,第 42 页,图 10. 向量表,此处(我的一些附加注释以蓝色显示,并以黄色突出显示):
-
初始程序计数器 (PC):如果您正在寻找程序开始运行的第一个字节,那么该地址位置就是 Reset向量(它是void Reset_Handler(void) 形式的函数,在程序集here in the startup file 中定义)并且这个4 字节的函数地址(通常)存储在闪存中,作为第二个字(4 字节)在g_pfnVectors 全局向量表中(同样,向量表(数组)通常在闪存中内存中;另见:见上图) ,因此可以从g_pfnVectors 数组中获取或读取Reset_Handler() 函数的地址,如下所示:
// The initial program run location (Program Counter (PC)), where the program
// begins to _run_, is the `Reset_Handler()` function, whose address is stored
// as the 2nd word (index 1) of the `g_pfnVectors` array.
uint32_t start_of_run_location_in_ram = g_pfnVectors[1];
见上图,以及下面的启动 .s 文件和链接描述文件 .ld “加载”文件。
-
汇编 .s “启动”文件和链接描述文件 .ld “加载”文件: 并注意 startup_stm32f767xx.s 启动文件将 g_pfnVectors 数组放在 @987654352 的开头@ 部分和STM32F767ZITx_FLASH.ld 链接器脚本将.isr_vector 部分存储为FLASH 中的第一件事。这意味着存储在闪存中的应用程序的第一个字节是g_pfnVectors 全局向量表数组的第一个字节。另外,您可以从上面的启动文件中看到,g_pfnVectors 全局向量表数组按此顺序存储了以下(4 字节)字:
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
/* etc. etc. */
注意初始堆栈指针(SP)存储为第一个(4字节)字,并设置为_estack,代表“堆栈结束”,是一个地址定义在上面的链接器脚本中。第二个字是Reset_Handler 函数的地址,它被定义为here in the startup file 并声明here in the linker script file 是程序入口点,或程序运行时位置 的开始。因此,Reset_Handler() 函数的地址是初始程序计数器 (PC)。以下是在链接描述文件中将其设置为入口点的方式:
/* Entry Point */
ENTRY(Reset_Handler)
-
总结:我再说一遍,我们在这里讨论 3 个独立且截然不同的事物:
-
程序在闪存中的存储位置:
start_of_program,它是在闪存中程序在闪存中存储的地址位置。阅读它:
uint32_t application_start_address = (uint32_t)&g_pfnVectors[0];
-
初始堆栈指针 (SP):
initial_stack_ptr_location_in_ram,它是 RAM 中堆栈指针开始的地址位置,用于在运行时将变量放置在程序堆栈。阅读它:
uint32_t initial_stack_ptr_location_in_ram = g_pfnVectors[0];
-
初始程序计数器 (PC):
start_of_run_location_in_ram,它是地址位置(通常在闪存中,但取决于您的链接描述文件和启动文件,因为您可以选择从 RAM 运行整个程序,如果您喜欢在程序启动时将其从闪存复制到 RAM,在启动文件的顶部)程序首先从哪里开始运行,以及您的Reset_Handler() 向量在哪个位置(“void(void )”函数)的位置。要“重新启动”您的应用程序,您需要做一些事情,然后调用此Reset_Handler() 函数以从头开始运行您的程序。从全局向量表中读取此Reset_Handler() 函数的地址:
uint32_t start_of_run_location_in_ram = g_pfnVectors[1];
-
更进一步: 或者,如果您想将此地址声明为函数指针,然后实际调用它,您可以这样做:
typedef void (*void_void_func_t)(void);
void_void_func_t reset_func = (void_void_func_t)g_pfnVectors[1];
reset_func();
或者,直接调用 Reset_Handler() 函数:
// Declare the existence of the function with a forward declaration
// since it's defined in the .s startup assembly file
void Reset_Handler(void);
Reset_Handler();
但是:请记住,您不应该随时“随意”地调用此重置功能。相反,STM32 文档在某处指出,在实际调用复位之前,您应该做一些事情来准备芯片以调用复位。所以,先做这几件事,然后在你想重新启动应用程序时调用 reset 函数。另请注意,重置微控制器的另一种(可能更安全/更容易)的方法是仅使用看门狗。将看门狗超时设置为最小,关闭看门狗的所有中断和搔痒,进入无限空循环,直到看门狗复位芯片。
相关: