Stack painting
Fill a stack with a known pattern at boot, then check later how much of the pattern has been overwritten.
A microcontroller without an MMU has no guard page to complain when a stack grows too far; it simply overwrites whatever lives next door. How large a stack must be is rarely computable up front, because interrupts, recursion and library calls add depth nobody wrote down. So you measure instead.
Startup code paints every stack with a pattern — FreeRTOS uses the byte 0xA5, others prefer 0xDEADBEEF — and after the system has run for a while you scan up from the bottom for the first word that changed. That distance is the high-water mark, the deepest the stack has ever been. FreeRTOS exposes it as uxTaskGetStackHighWaterMark(). It only tells you what happened during the test, not the worst case, so a margin gets added afterwards, usually with a sigh.