0

I could only find bits and pieces of information on the symbol _start, which is called from the target startup code in order to establish the C runtime environment. This would be necessary to ensure that all initialized global/static variables are properly loaded prior to branching to main().

In my case, I am using an MCU with an ARM Cortex-R4F core CPU. When the device resets, I implement all of the steps recommended by the MCU manufacturer then attempt to branch to the symbol _start using the following lines of code:

extern void _start(void);
_start();

I am using something similar to the following to link the program:

armeb-eabi-gcc-7.5.0" -marm -fno-exceptions -Og -ffunction-sections -fdata-sections -g -gdwarf-3 -gstrict-dwarf -Wall -mbig-endian -mcpu=cortex-r4 -Wl,-Map,"app_tms570_dev.map" --entry main -static -Wl,--gc-sections -Wl,--build-id=none -specs="nosys.specs" -o[OUTPUT FILE NAME HERE] [ALL OBJECT FILES HERE] -Wl,-T[LINKER COMMAND FILE NAME HERE]

My toolchain in this case is gcc-linaro-7.5.0-2019.12-i686-mingw32_armeb-eabi, which is being used since my MCU device is big-endian.

As I trace through the call to symbol _start, I can see my program branch to symbol _start then a few unexpected things happen.

First, there are a couple of places where the following instruction is called:

EF123456 svc #0x123456

This basically generates a software interrupt, which causes the program to branch to the software interrupt handler that I have configured for the device.

Secondly, the device eventually branches to __libc_init_array then _init. However, symbol _init does not contain any branch instruction and allows the program to flow into _fini, which also does not contain any branch instruction and allows the program to flow into whatever code was placed next in memory. This eventually causes some type of abort exception, as would be expected.

The disassembly associated with _init and _fini:

          _init():
00003b00:   E1A0C00D            mov        r12, r13
00003b04:   E92DDFF8            push       {r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r14, pc}
00003b08:   E24CB004            sub        r11, r12, #4
          _fini():
00003b0c:   E1A0C00D            mov        r12, r13
00003b10:   E92DDFF8            push       {r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r14, pc}
00003b14:   E24CB004            sub        r11, r12, #4

Based on some other documentation I read, I also attempted to call main() directly, but this just caused the program to jump to main() without initializing anything. I also tried to call symbol __main() similar to what is done when using the ARM Compiler in order to execute startup code, but this symbol is not found.

Note that this is for a bare-metal-ish system that does not use semihosting.

My question is: Is there a way to set up the system and call a function that will establish the C runtime environment automatically and branch to main() using the GCC linker?

For the time being, I have implemented my own function to initialize .data sections and the .bss sections are already being zeroed at reset using a built in feature of the MCU device.

Adding some more details here: The specific MCU that I am using should not be relevant, particularly taking the following discussion into consideration.

First, I have already set up the exception vectors for the device in an assembler file:

    .section .excvecs,"ax",%progbits
    .type   Exc_Vects, %object
    .size   Exc_Vects, .-Exc_Vects

// See DDI0363G, Table 3-6
Exc_Vects:
        b   c_int00        // Reset vector
        b   exc_undef      // Undefined instruction
        b   exc_software   // Software
        b   exc_prefetch   // Pre-fetch abort
        b   exc_data       // Data abort
        b   exc_invalid    // Invalid vector

There are two instructions that follow for the IRQ and FIQ interrupts as well, but they are set according to the MCU datasheet. I have defined handlers for the undefined instruction, prefetch abort, data abort and invalid vector exceptions. For the software exception I use some assembly to jump to an address that can be changed at runtime. My startup sequence begins at c_int00. These have all been tested and work with no problems.

My reset handler takes care of all of the steps needed for initializing the MCU in accordance with the MCU datasheet. This include initializing CPU registers and the stack pointers, which are loaded using symbols from the linker file.

The toolchain that I am using, noted above, includes the C standard libraries and other libraries needed to compile and link my program with no problems. This includes the symbol _start that I mentioned previously.

From what I understand, the function _start typically wraps main(). Before it calls main() it initializes .bss and .data sections, configures the heap, as well as performing some other tasks to set up the environment. When main() returns, it performs some clean up tasks and branches to a designated exit() function. (Side note: _start is defined in newlib based on the source code that I downloaded from linaro).

There is some detail regarding this in a separate response here:

What is the use of _start() in C?

I have been using the ARM Compiler as an alternative for the same project. There, __main performs these functions. For the stack initialization, I basically provide it an empty hook function and for exit I provide it with a function that safely terminates the program should main() return for some reason. I am not sure if something like this is needed for GCC.

I would note that I have included option -specs="nosys.specs" without option -nostartfiles. My understanding is that this avoids implementing some of the functions that do not want to use in my application, such as I/O operations, but links the startup code.

I am not using the heap in my project as dynamic memory use is frowned upon, but I was hoping to be able to use the startup code primarily in order to avoid having to remember to initialize .data sections manually. Above I noted that my application is baremetal-ish. I am actually using an RTOS and have the memory partitioned into blocks so that I can use the device MPU.

Jonathon S.
  • 1,928
  • 1
  • 12
  • 18
  • start with the bootstrap that comes with the C library you are using (compiler and C library are two different things). – old_timer Aug 12 '21 at 19:04
  • if you are not interested in a C library (yet) but just want to boot into some C code baremetal, then abandon all of this code you are looking at, it can be done quite trivially. – old_timer Aug 12 '21 at 19:05
  • you also need to create your own linker script _start is simply the entry point from the default linker script and for bare metal doesnt really mean anything, the entry point is based on how the processor boots and not ENTRY() in the bootstrap. often only needing to define a _start to keep the linker from warning, can leave _start out and just ignore the linker warning. – old_timer Aug 12 '21 at 19:06
  • if you want to take the complicated path that most folks take (grossly overcomplicated linker scripts, etc) then understand the linker script and the bootstrap are a pair, intimately related, the bootstrap generally wont work without the proper/matching linker script. – old_timer Aug 12 '21 at 19:08
  • please post the bootstrap code you have so far and anything else relevant to provide a minimum reproducible example. – old_timer Aug 12 '21 at 20:32

0 Answers0