【问题标题】:How to get value of variable defined in ld linker script from C如何从 C 获取 ld 链接器脚本中定义的变量的值
【发布时间】:2018-07-11 16:30:15
【问题描述】:

我正在编写一个程序来运行裸机。我正在尝试从自定义链接器脚本中获取要在 C 中使用的变量,这就是我所尝试的。

来自 C:

extern unsigned long* __START_OF_PROG_MEMORY;
volatile unsigned long *StartOfProgram = (unsigned long*) (&__START_OF_PROG_MEMORY);

链接脚本:

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;

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

【问题讨论】:

  • 你试过了,有没有报错或者只是瞎问?
  • @Gaurav 不,我没有遇到任何错误,但由于这是在运行裸机,因此我的调试能力受到服务器限制。

标签: c gcc linker


【解决方案1】:

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 微控制器的更多详细信息):

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

  2. 初始堆栈指针 (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. 向量表,此处(我的一些附加注释以蓝色显示,并以黄色突出显示):

  3. 初始程序计数器 (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 “加载”文件。

  4. 汇编 .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)
    
  5. 总结:我再说一遍,我们在这里讨论 3 个独立且截然不同的事物:

    1. 程序在闪存中的存储位置start_of_program,它是在闪存中程序在闪存中存储的地址位置。阅读它:
      uint32_t application_start_address = (uint32_t)&g_pfnVectors[0];
      
    2. 初始堆栈指针 (SP):initial_stack_ptr_location_in_ram,它是 RAM 中堆栈指针开始的地址位置,用于在运行时将变量放置在程序堆栈。阅读它:
      uint32_t initial_stack_ptr_location_in_ram = g_pfnVectors[0];
      
    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];
      
      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 函数。另请注意,重置微控制器的另一种(可能更安全/更容易)的方法是仅使用看门狗。将看门狗超时设置为最小,关闭看门狗的所有中断和搔痒,进入无限空循环,直到看门狗复位芯片。

相关:

【讨论】:

  • uint32_t application_start_address = (uint32_t)&amp;g_pfnVectors[0]; 这是非常非常错误的。它没有按照你的想法做
  • @0___________,我不同意。我认为这是非常非常正确的。它与uint32_t application_start_address = (uint32_t)g_pfnVectors; 相同。它获取 g_pfnVectors 数组的起始地址,这是 STM32 各种 ISR 的全局向量表,并将其转换为标准数字,我可以根据需要进行常规数学运算或打印,以便我可以跟踪程序起始地址在闪存中的确切位置,因为g_pfnVectors 是第一个。是什么让你说它“非常非常错误”? 你认为 我认为它做了什么?您将如何修复或更改它?
  • 应用起始地址与向量表起始地址不同。
  • @0___________,当然是。 startup_stm32f767xx.s 启动文件将g_pfnVectors 数组放在.isr_vector 部分的开头,而STM32F767ZITx_FLASH.ld 链接描述文件将.isr_vector 部分作为very 1st 存储在@987654381 中@.
  • STM32F767ZITx_FLASH.ld 链接器脚本甚至有一条注释说:/* The startup code goes first into FLASH */,而且启动文件再次显示启动代码中的第一件事是g_pfnVectors 向量表数组。所以,g_pfnVectors 是 flash 中的第一件事,在应用程序的开头。
【解决方案2】:

通常是这样的

// 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;

(见this post in Binutils ML)。

【讨论】:

  • volatile 根本不需要。 const unsigned char *StartOfProgram = __START_OF_PROG_MEMORY; 更正确
  • @0___________ 我试图保留 OP 的代码。但我认为您的建议是有效的,所以我进行了更改。
猜你喜欢
  • 2016-12-25
  • 2013-11-24
  • 1970-01-01
  • 2013-06-15
  • 2012-01-31
  • 1970-01-01
  • 1970-01-01
  • 2021-12-26
  • 1970-01-01
相关资源
最近更新 更多