Stack pointer is 0 in a bare metal AArch64 program

Orlando Arias orlandoarias@gmail.com
Mon May 11 16:00:53 GMT 2020


Greetings,

On 5/11/20 8:40 AM, Joel Sherrill wrote:
> Then why isn't a linker script provided which provides this? This behavior
> is in sharp contrast with other CPU-elf targets with libgloss support. They
> can produce executables out of the box with no requirements like this.

I am afraid I do not have a good answer for this. A cursory look
indicates that the default linker script for Aarch64 is being provided
by ld from binutils [or whatever other linker is being used], and not
newlib. The linker script in question may not fit the requirements of
the simulator, or vice versa. I believe a more proper question would be
``why does the default linker script not create a binary that is readily
runnable in the simulator?'' but I can not say whether this is proper,
and I do not have an answer either.

For actual hardware platforms, the norm is to provide your own linker
script. This is because the linker script helps tailor the binary to the
memory map of the device. For example, in the M profile for both
ARMv{6,7} and ARMv8, the SRAM region starts at address 0x20000000 and
can for up to 512 MiB [excluding any bit banding regions that may be
present]. However, an MCU using these architectures will have various
amounts of memory. The linker script then sets the stack pointer by
providing a symbol that gets populated at address 0 of the vector table.

In my linker script for an STM32F407, I have:

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(__reset)		/* the hardware does not care about this */

MEMORY {
    FLASH (rx)       : ORIGIN = 0x08000000, LENGTH = 1024K
    RAM (rxw)        : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS {
	.vector_table : {
		KEEP(*(.vector_table))
	} > FLASH

	/* [snip] */

	__ram_end = ORIGIN(RAM) + LENGTH(RAM);	/* initial msp value */
}

Then, in my initialization code:

	.syntax unified
	.thumb

	.section .vector_table, "aw"
	.globl __vector_table
__vector_table:
	.word __ram_end			/* initial stack pointer */
	.word __reset			/* reset vector/entry point */
	.word __vector_2		/* non-maskable interrupt */
	/* rest of the vector table */

	.section .text
	.global __reset
	.type   __reset, %function
__reset:
	/* initialize .data region */
	ldr r0, =__data_start
	ldr r1, =__data_end
	ldr r2, =__data_init
	/* more initialization code */

When the CPU boots, it reads the vector table, initializes msp [main
stack pointer] using the value in entry 0, then initializes pc using the
value in entry 1. It then goes into thread mode and starts executing the
__reset vector. Yes, this completely bypasses libgloss, but I don't
really use most of newlib's or libgloss's facilities on my projects,
only the sporadic call to some basic C function. I believe the simulator
instead uses whatever the ELF says the entry point is and initializes pc
using that value.

Technically speaking, on actual freestanding hardware we use whatever
CMSIS package the vendor gives us. The CMSIS package provides both
vector table initialization, and memory region initialization,
completely bypassing the C runtime code in libgloss. CMSIS can go to do
other things, like enabling the FPU, and setting up the PLL for the CPU
to achieve a desired frequency. For the most part, CMSIS-Core is
standard across vendors, with only minor variations in the vector table
creation, peripheral/clock initialization code, and linker scripts.

Cheers,
Orlando.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 195 bytes
Desc: OpenPGP digital signature
URL: <https://sourceware.org/pipermail/newlib/attachments/20200511/bf945100/attachment.sig>


More information about the Newlib mailing list