microzig icon indicating copy to clipboard operation
microzig copied to clipboard

Stack grows towards data

Open jrg1k opened this issue 10 months ago • 9 comments

The generated linker script places .bss after .data:

ENTRY(microzig_main);

MEMORY
{
  flash0    (rx!w) : ORIGIN = 0x00000000, LENGTH = 0x0009D800
  ram0      (rw!x) : ORIGIN = 0x20000000, LENGTH = 0x00040000
  ram1      (rw!x) : ORIGIN = 0x40100000, LENGTH = 0x00004000
}

SECTIONS
{
  .text :
  {
     KEEP(*(microzig_flash_start))
     *(.text*)
  } > flash0

  .ARM.extab : {
      *(.ARM.extab* .gnu.linkonce.armextab.*)
  } >flash0

  .ARM.exidx : {
      *(.ARM.exidx* .gnu.linkonce.armexidx.*)
  } >flash0

  .data :
  {
     microzig_data_start = .;
     *(.sdata*)
     *(.data*)
     *(.rodata*)
     microzig_data_end = .;
  } > ram0 AT> flash0

  .bss (NOLOAD) :
  {
      microzig_bss_start = .;
      *(.bss*)
      *(.sbss*)
      microzig_bss_end = .;
  } > ram0

  .flash_end :
  {
      microzig_flash_end = .;
  } > flash0

  microzig_data_load_start = LOADADDR(.data);
}

By ensuring the stack is the lowest item in ram a stack overflow causes a busfault instead of overwriting data.

jrg1k avatar Feb 08 '25 19:02 jrg1k

This is by design, a stack overflow is a stack overflow, and this way the hardware can easily detect it for us. If you'd like to make a case otherwise please make it.

mattnite avatar Feb 14 '25 21:02 mattnite

But the way it is currently the hardware doesn't necessarily detect it if it just silently overwrites data. It grows over .bss and .data. My suggestion is that the stack should have a known compile time size and be placed at the beginning of ram to avoid this.

jrg1k avatar Feb 16 '25 13:02 jrg1k

Oh I see, we agree on how we'd want our systems to behave. Your first comment was not clear which you thought was the ideal behaviour.

mattnite avatar Feb 16 '25 22:02 mattnite

Yes I was a bit lazy creating the issue. The way I currently do this in my bare-metal project is to create a separate stack section:

    .stack (NOLOAD) :
    {
        *(.stack)
    } > RAM

Then I configure the stack in zig:

export var stack: [0x800]u8 align(8) linksection(".stack") = undefined;

export var vector_table: VectorTable align(4) linksection(".vector_table") = .{
    .initial_stack_pointer = @as([*]u8, &stack) + stack.len,
    .reset_handler = Reset_Handler,
};

jrg1k avatar Feb 17 '25 14:02 jrg1k

I'm inclined to think that this is a better default. We would need to set a max size of the stack ahead of time, and we'd probably want that to be configurable.

Grazfather avatar Mar 19 '25 22:03 Grazfather

Yep, sounds like a good solution

We already have a config.zig file, we can utilize it for thid

ikskuh avatar Mar 19 '25 23:03 ikskuh

This is configurable by setting the end_of_stack option. Maybe we just change the default in build.zig? The issue is that it requires us to know ahead of time how much stack space we might need, rather than having .data and .bss at the start and (hopefully) a large gap before the stack.

Grazfather avatar Mar 25 '25 15:03 Grazfather

I'd think the default should be platform dependent.

Putting the stack first does not solve the overflow problem on all platforms. The ATmega328 for example starts internal SRAM at 0x0100 with the address lower than that being the various I/O registers.

Some platforms (like RP2350) have stack limit registers which should be used if a stack size is specifed.

Uthedris avatar Apr 03 '25 16:04 Uthedris

The RP2040 manual (section 2.6.2) goes into some detail about how ram is banked. The high banks (where the stack is currently placed) are unstriped "...guaranteeing that the processors never stall on these accesses." If I understand this correctly, placing the stack in low memory could increase the chances of conflict with access from the other core or from DMA stalling access to the stack until the other operation is complete. Perhaps not a major factor, but still an argument for moving the stack to low memory optional.

Uthedris avatar Apr 05 '25 13:04 Uthedris