如何从C获取ld链接程序脚本中定义的变量的值

Oli*_*ong 5 c linker gcc

我正在编写一个程序来运行裸机。我正在尝试从自定义链接描述文件中获取变量以在C中使用,这是我尝试过的。

从C:

extern unsigned long* __START_OF_PROG_MEMORY;
volatile unsigned long *StartOfProgram = (unsigned long*) (&__START_OF_PROG_MEMORY);
Run Code Online (Sandbox Code Playgroud)

链接描述文件:

SECTIONS
{
    . = 0x80000;
    PROVIDE(__START_OF_PROG_MEMORY = .);
    .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
    .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
    PROVIDE(_data = .);
    .data : { *(.data .data.* .gnu.linkonce.d*) }
    .bss (NOLOAD) : {
        . = ALIGN(16);
        __bss_start = .;
        *(.bss .bss.*)
        *(COMMON)
        __bss_end = .;
    }
    _end = .;
    PROVIDE(__END_OF_PROG_MEMORY = .);

   /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}
__bss_size = (__bss_end - __bss_start)>>3;
Run Code Online (Sandbox Code Playgroud)

是获取链接描述文件中定义的变量内容的正确方法吗?

Gab*_*les 10

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;
Run Code Online (Sandbox Code Playgroud)

然后执行复制的 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);
Run Code Online (Sandbox Code Playgroud)

请注意“&”运算符的使用。这些是正确的。或者,可以将符号视为向量或数组的名称,然后代码将再次按预期工作:

[==>这是我的首选方法<==]:

extern char start_of_ROM[], end_of_ROM[], start_of_FLASH[];

memcpy (start_of_FLASH, start_of_ROM, end_of_ROM - start_of_ROM);
Run Code Online (Sandbox Code Playgroud)

请注意如何使用此方法不需要使用“&”运算符。

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;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

瞧!这很神奇:)。

重要信息(有关 STM32 微控制器的更多详细信息):

  1. 存储在闪存中的应用程序/程序位置:不是application_start_address程序开始运行的第一个字节(这是初始程序计数器(PC)),也不是说它是程序堆栈内存的第一个字节从RAM(这是初始堆栈指针 (SP))开始。我的意思是它是闪存中存储程序的第一个字节。这里差别很大。为了管理闪存中的两个应用程序,例如,对于 OTA(空中下载)更新,我说的application_start_address闪存存储程序的第一个位置

  2. 初始堆栈指针 (SP):如果您在RAM中寻找堆栈内存开始的第一个位置,则该地址位置作为全局向量表中的第一个字(4 个字节)存储在闪存g_pfnVectors中(同样,通常在闪存中内存),并且可以像这样获取或读取:

    // Initial Stack Pointer (SP) value where the program stack begins.
    uint32_t initial_stack_ptr_location_in_ram = g_pfnVectors[0];
    
    Run Code Online (Sandbox Code Playgroud)

    参见编程手册PM0253,第 42 页,图 10。矢量表,这里(我的一些附加注释为蓝色,并以黄色突出显示): 在此处输入图片说明

  3. 初始程序计数器 (PC):如果您要查找程序开始运行的第一个字节,则该地址位置是重置向量(它是形式的函数void Reset_Handler(void),并在启动文件中的汇编定义)) 并且这个 4 字节函数地址(通常)存储在 flash 中,作为全局向量表中的第 2 个字(4 个字节)g_pfnVectors(同样,哪个向量表(数组)通常在闪存中;另外:参见上图),因此Reset_Handler()可以g_pfnVectors像这样从数组中获取或读取函数的地址:

    // 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];
    
    Run Code Online (Sandbox Code Playgroud)

    请参见上图,以及下面的启动 .s 文件和链接器脚本 .ld“加载”文件。

  4. 大会.S“启动”文件和链接脚本.LD“负荷”文件:并注意startup_stm32f767xx.s启动文件放置g_pfnVectors在开始阵列.isr_vector部分,与该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. */
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,初始堆栈指针 (SP) 存储为第一个(4 字节)字,并设置为_estack,代表“堆栈结束”,是上面链接描述文件中定义的地址。第二个字是Reset_Handler函数的地址,它在启动文件中定义并在链接描述文件中声明为程序入口点,或程序运行时位置的开始。因此,该Reset_Handler()函数的地址是初始程序计数器 (PC)。以下是如何将其设置为链接描述文件中的入口点:

    /* Entry Point */
    ENTRY(Reset_Handler)
    
    Run Code Online (Sandbox Code Playgroud)
  5. 总结:我再说一遍,我们在这里讨论3 个独立且不同的事物:

    1. 在闪存存储程序位置:start_of_program,这是地址位置闪光灯在程序存储在闪存中。阅读它:
      uint32_t application_start_address = (uint32_t)&g_pfnVectors[0];
      
      Run Code Online (Sandbox Code Playgroud)
    2. 初始堆栈指针(SP):initial_stack_ptr_location_in_ram,这是地址位置在RAM其中堆栈指针开始,用于向在运行时被放置在上变量程序栈。阅读它:
      uint32_t initial_stack_ptr_location_in_ram = g_pfnVectors[0];
      
      Run Code Online (Sandbox Code Playgroud)
    3. 初始程序计数器(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];
      
      Run Code Online (Sandbox Code Playgroud)
      1. 更进一步:或者,如果您想将此地址声明为函数指针,然后实际调用它,您可以这样做:
        typedef void (*void_void_func_t)(void);
        void_void_func_t reset_func = (void_void_func_t)g_pfnVectors[1];
        reset_func();
        
        Run Code Online (Sandbox Code Playgroud) 或者,只需调用 Reset_Handler()func :
        // 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();
        
        Run Code Online (Sandbox Code Playgroud) 但是:请记住,您不应该在任何时候都“随意”地调用此重置函数。相反,STM32 文档在某处指出,在实际调用 reset 之前,您应该做一些事情来准备芯片以调用 reset。所以,先做这几件事,然后在你想重新启动应用程序时调用重置函数。另请注意,另一种(可能更安全/更容易)重置微控制器的方法是仅使用看门狗。将看门狗超时设置为最小,关闭看门狗的所有中断和挠痒痒,进入无限空循环,直到看门狗复位芯片。

有关的:


yug*_*ugr 3

通常它是这样做的

// Volatile is normally not needed but it seems you have a special case
extern unsigned char __START_OF_PROG_MEMORY[];
unsigned char * const StartOfProgram = &__START_OF_PROG_MEMORY;
Run Code Online (Sandbox Code Playgroud)

(请参阅Binutils ML 中的这篇文章)。