Stack pointer is 0 in a bare metal AArch64 program

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Stack pointer is 0 in a bare metal AArch64 program

Sourceware - newlib list mailing list
Hi,

Following this thread:

https://sourceware.org/pipermail/gdb/2020-May/048516.html

I've been trying to build a bare metal AArch64 program and run it in the
binutils-gdb simulator.  The program is just an empty main function, and
it is compiled with:

$ aarch64-none-elf-gcc test.c -specs=nosys.specs -g3 -O0

When I try to run it, I get:

$ ./sim/aarch64/run --trace=on --trace-disasm=on ./sim/aarch64/a.out
memory:   ERROR: executable is too big: ffffffffffffffff
insn:      pc = 400168 instr = 58000281
disasm:   ldr   x1, 0x00000000004001b8
memory:   read of 0 (8 bytes) from 4001b8
insn:      pc = 40016c instr = 927cec20
disasm:   and   x0, x1, #0xfffffffffffffff0
insn:      pc = 400170 instr = 9100001f
disasm:   mov   sp, x0
insn:      pc = 400174 instr = d280001d
disasm:   mov   x29, #0x0                       // #0
insn:      pc = 400178 instr = a9bf77fd
disasm:   stp   x29, x29, [sp, #-16]!
memory:   write of 0 (8 bytes) to fffffffffffffff0
core: 8 byte write to unmapped address 0xfffffff0 at 0x0
program stopped with signal 11 (Segmentation fault).

I understand that these instructions try to set up the stack pointer, reading its initial
value from 0x4001b8.  This value happens to be 0, not a good value for a stack pointer.

I'm guessing that these instructions come from crt0.S, from newlib/libgloss:

https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=libgloss/aarch64/crt0.S;h=f831be12e6a17e67908dba3e6ffbe5de4e3b58a0;hb=HEAD#l141

With the steps I've done, is it expected that the stack pointer is 0?  Is there something
I'm missing to make it have some value that makes more sense?

Simon
Reply | Threaded
Open this post in threaded view
|

Re: Stack pointer is 0 in a bare metal AArch64 program

Sourceware - newlib list mailing list
Greetings,

On 5/10/20 10:31 PM, Simon Marchi via Newlib wrote:

> $ ./sim/aarch64/run --trace=on --trace-disasm=on ./sim/aarch64/a.out
> memory:   ERROR: executable is too big: ffffffffffffffff
> insn:      pc = 400168 instr = 58000281
> disasm:   ldr   x1, 0x00000000004001b8
> memory:   read of 0 (8 bytes) from 4001b8
> insn:      pc = 40016c instr = 927cec20
> disasm:   and   x0, x1, #0xfffffffffffffff0
> insn:      pc = 400170 instr = 9100001f
> disasm:   mov   sp, x0
> insn:      pc = 400174 instr = d280001d
> disasm:   mov   x29, #0x0                       // #0
> insn:      pc = 400178 instr = a9bf77fd
> disasm:   stp   x29, x29, [sp, #-16]!
Within libgloss for Aarch64, the stack is initialized using a weak
symbol with the value of 0:

.macro GEN_DWORD name
#if defined(__ILP32__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    .word \name
    .word 0
#elif defined(__ILP32__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    .word 0
    .word \name
#else
    .dword \name
#endif
.endm

.Lstack:
    GEN_DWORD __stack
    .weak __stack


If the linker script you are utilizing does not define __stack, then the
weak symbol is used.

As an aside, this behavior in libgloss has always annoyed me. When
working with Cortex-M cores [both ARMv7-M and ARMv8-M], msp is
initialized using the value stored in address 0 of the memory map by the
hardware. Yes, the application is free to change it afterwards, but it
just seems redundant to me.

Cheers,
Orlando.


signature.asc (201 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Stack pointer is 0 in a bare metal AArch64 program

Joel Sherrill
On Sun, May 10, 2020 at 11:04 PM Orlando Arias via Newlib <
[hidden email]> wrote:

> Greetings,
>
> On 5/10/20 10:31 PM, Simon Marchi via Newlib wrote:
> > $ ./sim/aarch64/run --trace=on --trace-disasm=on ./sim/aarch64/a.out
> > memory:   ERROR: executable is too big: ffffffffffffffff
> > insn:      pc = 400168 instr = 58000281
> > disasm:   ldr   x1, 0x00000000004001b8
> > memory:   read of 0 (8 bytes) from 4001b8
> > insn:      pc = 40016c instr = 927cec20
> > disasm:   and   x0, x1, #0xfffffffffffffff0
> > insn:      pc = 400170 instr = 9100001f
> > disasm:   mov   sp, x0
> > insn:      pc = 400174 instr = d280001d
> > disasm:   mov   x29, #0x0                       // #0
> > insn:      pc = 400178 instr = a9bf77fd
> > disasm:   stp   x29, x29, [sp, #-16]!
>
> Within libgloss for Aarch64, the stack is initialized using a weak
> symbol with the value of 0:
>
> .macro GEN_DWORD name
> #if defined(__ILP32__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
>     .word \name
>     .word 0
> #elif defined(__ILP32__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
>     .word 0
>     .word \name
> #else
>     .dword \name
> #endif
> .endm
>
> .Lstack:
>     GEN_DWORD __stack
>     .weak __stack
>
>
> If the linker script you are utilizing does not define __stack, then the
> weak symbol is used.
>
> As an aside, this behavior in libgloss has always annoyed me. When
> working with Cortex-M cores [both ARMv7-M and ARMv8-M], msp is
> initialized using the value stored in address 0 of the memory map by the
> hardware. Yes, the application is free to change it afterwards, but it
> just seems redundant to me.
>

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.

--joel
RTEMS



>
> Cheers,
> Orlando.
>
>
Reply | Threaded
Open this post in threaded view
|

Re: Stack pointer is 0 in a bare metal AArch64 program

Sourceware - newlib list mailing list
Hi,


On Mon, 11 May 2020 at 14:47, Joel Sherrill <[hidden email]> wrote:

>
> On Sun, May 10, 2020 at 11:04 PM Orlando Arias via Newlib <
> [hidden email]> wrote:
>
> > Greetings,
> >
> > On 5/10/20 10:31 PM, Simon Marchi via Newlib wrote:
> > > $ ./sim/aarch64/run --trace=on --trace-disasm=on ./sim/aarch64/a.out
> > > memory:   ERROR: executable is too big: ffffffffffffffff
> > > insn:      pc = 400168 instr = 58000281
> > > disasm:   ldr   x1, 0x00000000004001b8
> > > memory:   read of 0 (8 bytes) from 4001b8
> > > insn:      pc = 40016c instr = 927cec20
> > > disasm:   and   x0, x1, #0xfffffffffffffff0
> > > insn:      pc = 400170 instr = 9100001f
> > > disasm:   mov   sp, x0
> > > insn:      pc = 400174 instr = d280001d
> > > disasm:   mov   x29, #0x0                       // #0
> > > insn:      pc = 400178 instr = a9bf77fd
> > > disasm:   stp   x29, x29, [sp, #-16]!
> >
> > Within libgloss for Aarch64, the stack is initialized using a weak
> > symbol with the value of 0:
> >
> > .macro GEN_DWORD name
> > #if defined(__ILP32__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
> >     .word \name
> >     .word 0
> > #elif defined(__ILP32__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
> >     .word 0
> >     .word \name
> > #else
> >     .dword \name
> > #endif
> > .endm
> >
> > .Lstack:
> >     GEN_DWORD __stack
> >     .weak __stack
> >
> >
> > If the linker script you are utilizing does not define __stack, then the
> > weak symbol is used.
> >
> > As an aside, this behavior in libgloss has always annoyed me. When
> > working with Cortex-M cores [both ARMv7-M and ARMv8-M], msp is
> > initialized using the value stored in address 0 of the memory map by the
> > hardware. Yes, the application is free to change it afterwards, but it
> > just seems redundant to me.
> >
>
> 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 think this is also related to:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66203

> --joel
> RTEMS
>
>
>
> >
> > Cheers,
> > Orlando.
> >
> >
Reply | Threaded
Open this post in threaded view
|

Re: Stack pointer is 0 in a bare metal AArch64 program

Sourceware - newlib list mailing list
In reply to this post by Joel Sherrill
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.


signature.asc (201 bytes) Download Attachment