12

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?

Oliver Strong
  • 415
  • 1
  • 7
  • 15

2 Answers2

15

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:

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • `uint32_t application_start_address = (uint32_t)&g_pfnVectors[0]; ` it is very very wrong. It does not doing what your think – 0___________ May 18 '21 at 21:01
  • @0___________, I disagree. I consider it to be very very right. It is the same thing as `uint32_t application_start_address = (uint32_t)g_pfnVectors;`. It grabs the address of the start of the `g_pfnVectors` array, which is the global vector table for the STM32's various ISRs, and casts it to a standard number I can do regular math with or print, as needed, so I can keep track of the exact locations of the program start address in flash memory, since `g_pfnVectors` is first. What makes you say it is "very very wrong"? What do _you think_ that _I think_ it does? How would you fix or change it? – Gabriel Staples May 18 '21 at 22:30
  • Application start address is not the same as start address of the vector table. – 0___________ May 18 '21 at 22:38
  • @0___________, sure it is. The [startup_stm32f767xx.s](https://github.com/STMicroelectronics/STM32CubeF7/blob/master/Projects/STM32F767ZI-Nucleo/Examples/FLASH/FLASH_EraseProgram/SW4STM32/startup_stm32f767xx.s#L120) startup file places the `g_pfnVectors` array at the start of the `.isr_vector` section, & the [STM32F767ZITx_FLASH.ld](https://github.com/STMicroelectronics/STM32CubeF7/blob/master/Projects/STM32F767ZI-Nucleo/Examples/FLASH/FLASH_EraseProgram/SW4STM32/STM32F767ZI-Nucleo/STM32F767ZITx_FLASH.ld#L51) linker script stores the `.isr_vector` section as the _very 1st_ thing in `FLASH`. – Gabriel Staples May 18 '21 at 23:04
  • The [STM32F767ZITx_FLASH.ld](https://github.com/STMicroelectronics/STM32CubeF7/blob/master/Projects/STM32F767ZI-Nucleo/Examples/FLASH/FLASH_EraseProgram/SW4STM32/STM32F767ZI-Nucleo/STM32F767ZITx_FLASH.ld#L51) linker script even has a comment which says: `/* The startup code goes first into FLASH */`, and again, the startup file shows that the first thing in the startup code is the `g_pfnVectors` vector table array. So, `g_pfnVectors` is the very first thing in flash, at the start of your application. – Gabriel Staples May 18 '21 at 23:07
  • Furthermore, Programming Manual [PM0253](https://www.st.com/resource/en/programming_manual/dm00237416-stm32f7-series-and-stm32h7-series-cortexm7-processor-programming-manual-stmicroelectronics.pdf), pg 42, _Figure 10. Vector table_, shows that Offset 0x0000, or the first 4-byte word at the start of `g_pfnVectors`, contains the "Initial SP value", or the "initial stack pointer" value, where the program will begin running. The startup script .s file sets that to `_estack`, which is defined in the linker script above on line 36 to be the end of the RAM, since that's where the stack starts. – Gabriel Staples May 18 '21 at 23:13
  • @0___________, you seem to be ignoring the startup file I referenced, and not even looking for yourself, [here](https://github.com/STMicroelectronics/STM32CubeF7/blob/master/Projects/STM32F767ZI-Nucleo/Examples/FLASH/FLASH_EraseProgram/SW4STM32/startup_stm32f767xx.s#L120). I repeat: `g_pfnVectors` is the first thing in the `.isr_vector` section, and that section is the first thing in `FLASH`. – Gabriel Staples May 18 '21 at 23:16
  • No program will not begin running there. On startup the processor loads the value from the first 4 bytes of the vector table and sets the SP register with it. Next it reads the next 4 bytes and jumps to that address. That second 4 bytes is the address of the Reset Handler and the program execution starts there. – 0___________ May 18 '21 at 23:16
  • @0___________, I agree. I do _not_ mean `application_start_address` to be the first place in RAM where the program begins to _run_. I mean it to be _the first byte in flash where the program is **stored**_. Big difference here. I think this was a misunderstaning. For the sake of managing two applications in _flash_ memory, for OTA updates, I am talking about `application_start_address` being the first place in _flash_ where the program is _stored._ – Gabriel Staples May 18 '21 at 23:19
  • Answer updated greatly in section 3 (titled ''3. Note that if you're doing this for STM32 microcontrollers") with a long section marked "IMPORTANT". – Gabriel Staples May 19 '21 at 03:26
3

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).

AJM
  • 1,317
  • 2
  • 15
  • 30
yugr
  • 19,769
  • 3
  • 51
  • 96
  • 1
    volatile is not needed at all. `const unsigned char *StartOfProgram = __START_OF_PROG_MEMORY;` is much more correct – 0___________ May 18 '21 at 21:02
  • @0___________ I tried to preserve the OP's code. But I think your suggestion is valid so I made the changes. – yugr May 19 '21 at 15:55