Search code examples
memorylinkerheap-memorymicrocontrollerstack-memory

In memory, should stack bottom and heap bottom have the same address?


I'm using a tm4c123gh6pm MCU with this linker script. Going to the bottom, I see:

...
...
.bss (NOLOAD):
{
    _bss = .;
    *(.bss*)
    *(COMMON)
    _ebss = .;
} > SRAM

_heap_bottom = ALIGN(8);
_heap_top = ORIGIN(SRAM) + LENGTH(SRAM) - _stack_size;
_stack_bottom = ALIGN(8);
_stack_top = ORIGIN(SRAM) + LENGTH(SRAM);

It seems that heap and stack bottoms are the same. I have double checked it:

> arm-none-eabi-objdump -t mcu.axf | grep -E "(heap|stack)"
20008000 g       .bss   00000000 _stack_top
20007000 g       .bss   00000000 _heap_top
00001000 g       *ABS*  00000000 _stack_size
20000558 g       .bss   00000000 _heap_bottom
20000558 g       .bss   00000000 _stack_bottom

Is this correct? As far as I can see, the stack could overwrite the heap, is this the case?

If I flash this FW it 'works' (at least for now), but I'm expecting it to fail if the stack gets big enough and I use dynamic memory. I have observed though that no one in my code or the startup script uses the stack and bottom symbols, so maybe even if I use the stack and heap everything keeps working. (Unless the stack and heap are special symbols used by someone I can't see, is this the case?)

I want to change the last part by:

_heap_bottom = ALIGN(8);
_heap_top = ORIGIN(SRAM) + LENGTH(SRAM) - _stack_size;
_stack_bottom = ORIGIN(SRAM) + LENGTH(SRAM) - _stack_size + 4; // or _heap_top + 4
_stack_top = ORIGIN(SRAM) + LENGTH(SRAM);

Is the above correct?


Solution

  • If you write your own linker script then it is up to you how stack and heap are arranged.

    One common approach is to have stack and heap in the same block, with stack growing downwards from the highest address towards the lowest, and heap growing upwards from a lower address towards the highest.

    The advantage of this approach is that you don't need to calculate how much heap or stack you need separately. As long as the total of stack and heap used at any one instant is less than the total memory available, then everything will be ok.

    The disadvantage of this approach is that when you allocate more memory than you have, your stack will overflow into your heap or vice-versa, and your program will fail in a variety of ways which are very difficult to predict or to identify when they happen.

    The linker script in your question uses this approach, but appears to have a mistake detailed below.

    Note that using the names top and bottom when talking about stacks on ARM is very unhelpful because when you push something onto the stack the numerical value of the stack pointer decreases. When the stack is empty the stack pointer has its highest value, and when it is full the stack pointer has its lowest value. It is ambiguous whether "top" refers to the highest address or the location of the current pointer, and whether bottom refers to the lowest address or the address where the first item is pushed.

    In the CMSIS example linker scripts the lower and upper bounds of the heap are called __heap_base and __heap_limit, and the lower and upper bounds of the stack are called __stack_limit and __initial_sp respectively.

    In this script the symbols have the following meanings:

    _heap_bottom is the lowest address of the heap.

    _heap_top is the upper address that the heap must not grow beyond if you want to leave at least _stack_size bytes for the stack.

    For _stack_bottom, it appears that the script author probably mistakenly thought that ALIGN(8) would align the most recently assigned value, and so they wanted _stack_bottom to be an aligned version of _heap_top, which would make it the value of the stack pointer when _stack_size bytes are pushed to it. In fact ALIGN(8) aligns the value of ., which still has the same value as _heap_bottom as you have observed.

    Finally _stack_top is the highest address in memory, it is the value the stack pointer will start with when the stack is empty.

    Having an incorrect value for the stack limit almost certainly does absolutely nothing at all, because this symbol is probably never used in the code. On this ARMv7M processor the push and pop instructions and other accesses to the stack by hardware assume that the stack is an infinite resource. Compilers using all the normal ABIs also generate code which does not check before growing the stack either. The reason for this is that it is one of the most common operations performed, and so adding extra instructions would cripple performance. The next generation ARMv8M does have hardware support for a stack limit register, though.

    My advice to you is just delete the line. If anything is using it, then you are basically losing the whole benefit of sharing your stack and heap space. If you do want to calculate and check for it, then your suggestion is correct except that you don't need to add + 4. This would create a 4 byte gap which is not usable as either heap or stack.

    As an aside, I personally prefer to put the stack at the bottom of memory and the heap at the top, growing way from each other. That way, if either of them get bigger than they should they go into an unallocated address space which can be configured to cause a bus fault straight away, without any software checking the values all the time.