How to get value of variable defined in ld linker script from C

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?


Solution 1:

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): enter image description here

  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:

  • [My Q&A: See both the Question & Answer here for excellent info & more examples!] Is accessing the "value" of a linker script variable undefined behavior in C?
  • [My Question] Why do STM32 gcc linker scripts automatically discard all input sections from these standard libraries: libc.a, libm.a, libgcc.a?

Solution 2:

Typically it's done like

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

(see this post in Binutils ML).