Stack grows towards data
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.
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.
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.
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.
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,
};
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.
Yep, sounds like a good solution
We already have a config.zig file, we can utilize it for thid
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.
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.
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.