如何从 C 获取 ld 链接器脚本中定义的变量的值 [英] How to get value of variable defined in ld linker script from C

查看:18
本文介绍了如何从 C 获取 ld 链接器脚本中定义的变量的值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

来自 C:

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

链接脚本:

部分{.= 0x80000;提供(__START_OF_PROG_MEMORY = .);.text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }提供(_data = .);.data : { *(.data .data.* .gnu.linkonce.d*) }.bss(无负载):{.=对齐(16);__bss_start = .;*(.bss .bss.*)*(常见的)__bss_end = .;}_end = .;提供(__END_OF_PROG_MEMORY = .);/DISCARD/: { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }}__bss_size = (__bss_end - __bss_start)>>3;

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

解决方案

1.访问源代码中链接脚本变量的官方文档:

查看本页底部的示例:

  • 初始程序计数器 (PC):如果您正在寻找程序开始运行的第一个字节,那么该地址位置就是 复位向量(它是 void Reset_Handler(void) 形式的函数,在汇编 在启动文件中),这个4字节的函数地址是(通常)存储在闪存中,作为g_pfnVectors全局向量表中的第2个字(4字节)(同样,哪个向量表(数组) 通常在 闪存中 内存中;另见上图),因此 Reset_Handler() 函数的地址可以从 中获取或读取>g_pfnVectors 这样的数组:

    //程序的初始运行位置(Program Counter (PC)),程序所在的位置//开始_run_,是`Reset_Handler()`函数,存放地址//作为 `g_pfnVectors` 数组的第二个单词(索引 1).uint32_t start_of_run_location_in_ram = g_pfnVectors[1];

    参见上图,以及启动 .s 文件和链接描述文件 .ld 加载"下面的文件.

  • 组装 .s 启动"文件和链接描述文件 .ld load"文件: 并注意 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/* 等等等等 */

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

    /* 入口点 */ENTRY(Reset_Handler)

  • 总结:我再说一遍,我们在这里讨论的是 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,在启动文件的顶部)程序首先从哪里开始运行,以及您的 在哪个位置code>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_Handler() 函数:

        //用前向声明声明函数的存在//因为它是在 .s 启动程序集文件中定义的无效重置处理程序(无效);Reset_Handler();

        但是:请记住,您不应该只是随便"地调用此重置功能.无论你什么时候想要.相反,STM32 文档在某处指出,在实际调用复位之前,您应该做一些事情来准备芯片以调用复位.所以,先做这几件事,然后在你想重新启动应用程序时调用 reset 函数.另请注意,重置微控制器的另一种(可能更安全/更容易)的方法是仅使用看门狗.将看门狗超时设置为最小,关闭看门狗的所有中断和搔痒,进入无限空循环,直到看门狗复位芯片.

  • 相关:

    I am writing a program to run bare metal. I am trying to get a variable from a custom linker script to use in C here is what I have attempted.

    From C:

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

    Linker Script:

    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;
    

    Is the the correct way to get the contents of the variable defined in the linker script?

    解决方案

    1. Official documentation to access linkerscript variables in your source code:

    See examples at the bottom of this page: https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html

    Hence when you are using a linker script defined symbol in source code you should always take the address of the symbol, and never attempt to use its value. For example suppose you want to copy the contents of a section of memory called .ROM into a section called .FLASH and the linker script contains these declarations:

    start_of_ROM   = .ROM;
    end_of_ROM     = .ROM + sizeof (.ROM);
    start_of_FLASH = .FLASH;
    

    Then the C source code to perform the copy would be:

    extern char start_of_ROM, end_of_ROM, start_of_FLASH;
    
    memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);
    

    Note the use of the ‘&’ operators. These are correct. Alternatively the symbols can be treated as the names of vectors or arrays and then the code will again work as expected:

    [==> This is my preferred approach <==]:

    extern char start_of_ROM[], end_of_ROM[], start_of_FLASH[];
    
    memcpy (start_of_FLASH, start_of_ROM, end_of_ROM - start_of_ROM);
    

    Note how using this method does not require the use of ‘&’ operators.

    2. Your specific case:

    So, if I wanted to grab the value of the linkerscript variable __START_OF_PROG_MEMORY for use in my C program, I'd do:

    #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. Note that if you're doing this for STM32 microcontrollers:

    Another trick to grab the address of the start of the program memory (usually Flash--from where the start of the program is stored) is to simply grab the address of the g_pfnVectors global ISR vector table array, which is defined in your startup assembly file (ex: "startup_stm32f746xx.s"). To do that, do the following:

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

    Voilá! It's magical :).

    IMPORTANT (tons more details on STM32 microcontrollers):

    1. Stored application/program location in flash: I do not mean application_start_address to be the first byte where the program begins to run (which is the initial Program Counter (PC)), nor do I mean it to be the first byte where the program stack memory starts in RAM (which is the initial Stack Pointer (SP)). I mean it to be the first byte in flash where the program is stored. Big difference here. For the sake of managing two applications in flash memory, for OTA (Over the Air) updates, for instance, I am talking about application_start_address being the first place in flash where the program is stored.

    2. Initial Stack Pointer (SP): if you're looking for the first place in RAM where the stack memory begins, that address location is stored in flash as the 1st word (4 bytes) in the g_pfnVectors global vector table (again, usually in flash memory), and can be obtained, or read, like this:

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

      See Programming Manual PM0253, pg 42, Figure 10. Vector table, here (with a few of my additional notes in blue, and highlighting in yellow):

    3. Initial Program Counter (PC): and if you're looking for the first byte where the program begins to run, that address location is the Reset vector (which is a function of the form void Reset_Handler(void), and is defined in assembly here in the startup file) and this 4-byte function address is (usually) stored in flash, as the 2nd word (4 bytes) in the g_pfnVectors global vector table (again, which vector table (array) is usually in flash memory; also: see the image above), and therefore the address to the Reset_Handler() function can be obtained, or read, from the g_pfnVectors array like this:

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

      See image above, and the startup .s file and linker script .ld "load" file below.

    4. Assembly .s "startup" file, and linker script .ld "load" files: and note that the startup_stm32f767xx.s startup file places the g_pfnVectors array at the start of the .isr_vector section, & the STM32F767ZITx_FLASH.ld linker script stores the .isr_vector section as the very 1st thing in FLASH. This means that the very first byte of the application, as stored in flash memory, is the first byte of the g_pfnVectors global vector table array. Also, you can see from the startup file above that the g_pfnVectors global vector table array stores the following (4-byte) words, in this order:

      g_pfnVectors:
        .word  _estack
        .word  Reset_Handler
      
        .word  NMI_Handler
        .word  HardFault_Handler
        .word  MemManage_Handler
        .word  BusFault_Handler
        /* etc. etc. */
      

      Notice that the initial Stack Pointer (SP) is stored as the first (4-byte) word, and is set as _estack, which stands for "end of the stack", and is an address defined in the linker script above. The 2nd word is the address to the Reset_Handler function, which is defined here in the startup file and declared here in the linker script file to be the program entry point, or start of the run-time location of the program. The address of the Reset_Handler() function is therefore the initial Program Counter (PC). Here is how it is set as the entry point in the linker script:

      /* Entry Point */
      ENTRY(Reset_Handler)
      

    5. Summary: I repeat, we are talking about 3 separate and distinct things here:

      1. Stored program location in flash: the start_of_program, which is the address location in flash where the program is stored in flash. Read it with:

        uint32_t application_start_address = (uint32_t)&g_pfnVectors[0];
        

      2. Initial Stack Pointer (SP): the initial_stack_ptr_location_in_ram, which is the address location in RAM where the Stack Pointer begins, for variables to be placed at run-time on the program stack. Read it with:

        uint32_t initial_stack_ptr_location_in_ram = g_pfnVectors[0];
        

      3. Initial Program Counter (PC): the start_of_run_location_in_ram, which is the address location (usually in flash, but depends on your linker script and startup file, as you can optionally run your entire program from RAM if you like by copying it from flash to RAM at program startup, inside the top of the startup file) where the program first starts running from, and at which location your Reset_Handler() vector ("void(void)" function) is located. To "restart" your application, you need to do a handful of things and then call this Reset_Handler() function to begin running your program from the beginning. Read the address to this Reset_Handler() function from the global vector table with:

        uint32_t start_of_run_location_in_ram = g_pfnVectors[1];
        

        1. Going further: or, if you'd like to declare this address as a function pointer and then actually call it, you can do so like this:

          typedef void (*void_void_func_t)(void);
          void_void_func_t reset_func = (void_void_func_t)g_pfnVectors[1];
          reset_func();
          

          Or, just call the Reset_Handler() func directly:

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

          BUT: keep in mind you shouldn't just go calling this reset function all "willy nilly" whenever you want. Rather, the STM32 documentation states somewhere there are a few things you should do to prepare the chip for calling reset before you actually call reset. So, do those few things first, then call the reset function whenever you'd like to restart the application. Note also that another (and probably safer/easier) way to reset the microcontroller is to just use the watchdog. Set the watchdog timeout to the minimum, turn off all interrupts and tickling of the watchdog, and enter an infinite empty loop until the watchdog resets the chip.

    Related:

    这篇关于如何从 C 获取 ld 链接器脚本中定义的变量的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆