I'm working on a trace module which has to monitor FreeRTOS tasks' heap in order to detect stack overflows. I am wondering whether it is possible to get a task stack size after its creation. Can I get access to this information through the API? Or, is it stored in some internal structure? How do I get access to it?
I am wondering whether it is possible to get a task stack size after its creation.
Can I get access to this information through the API?
Or, is it stored in some internal structure? How do I get access to it?
Great question! Yes, yes you can get this information.
As of FreeRTOS V10.0.0, you'll see two really important additions in the History.txt
file here:
Changes between FreeRTOS V9.0.1 and FreeRTOS V10.0.0:
...
Introduced
configRECORD_STACK_HIGH_ADDRESS
. When set to1
the stack start address is saved into each task's TCB (assuming stack grows down).Introduced
configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H
to allow user defined functionality, and user defined initialisation, to be added to FreeRTOS'stasks.c
source file. WhenconfigINCLUDE_FREERTOS_TASK_C_ADDITIONS_H
is set to1
a user provided header file calledfreertos_task_c_additions.h
will be included at the bottom oftasks.c
. Functions defined in that header file can callfreertos_tasks_c_additions_init()
, which in turn calls a macro calledFREERTOS_TASKS_C_ADDITIONS_INIT()
, if it is defined.FREERTOS_TASKS_C_ADDITIONS_INIT()
can be defined inFreeRTOSConfig.h
.
See usStackHighWaterMark
in the TaskStatus_t
struct in task.h
. Even better: use these two functions:
uxTaskGetStackHighWaterMark();
uxTaskGetStackHighWaterMark2();
See: https://www.freertos.org/uxTaskGetStackHighWaterMark.html
There is also a really nice convenience function called vTaskList()
and vTaskListTasks()
from task.h
that prints out a table of all tasks, including their stack high water mark.
See here: https://www.freertos.org/a00021.html#vTaskList
taskGetStackSizeWords()
and taskGetStackSizeBytes()
functions to get stack size/depth in words and bytesSo, to get the stack depth (size) in words or bytes, you can do the following steps:
In your custom FreeRTOSConfig.h
file, add:
#define configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H 1
#define configRECORD_STACK_HIGH_ADDRESS 1
Create a file called freertos_task_c_additions.h
. Since you defined configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H
as 1
above, it will be automatically included by FreeRTOS directly into the very bottom of their tasks.c
file, giving your file direct access to all of the private members and static
content in tasks.c
. Defining configRECORD_STACK_HIGH_ADDRESS
as 1
above adds pxEndOfStack
, which we need in the stack size calculation below, to the task control block (TCB_t
) struct in tasks.c
. FreeRTOS tells us in tasks.c
that pxEndOfStack
"points to the highest valid address for the stack", meaning: the end of the stack. pxStack
, on the other hand, points to the beginning of the stack. So, the stack size is pxEndOfStack - pxStack + 1
.
In your freertos_task_c_additions.h
file, add the following:
// NB: THIS IS TYPICALLY NOT A HEADER FILE YOU SHOULD INCLUDE IN YOUR CODE.
// - Treat this like a .c source file. FreeRTOS includes this file instead.
// - The file you should include to access these public functions below is
// `freertos_task_c_additions_include.h`.
#pragma once
#include "FreeRTOS.h"
#if ((configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1) \
&& (configRECORD_STACK_HIGH_ADDRESS == 1))
// Get the stack size in words, not bytes, first passed to `xTaskCreate()`
// when the task was created. This is the size of the stack allocated for
// the task.
// - Note: due to the alignment that FreeRTOS enforces to the top (end) of
// the stack via `portBYTE_ALIGNMENT_MASK` in tasks.c, the available stack
// size is actually one or two bytes or so less than what you input at
// task creation. FreeRTOS adjusts `pxTopOfStack`, which gets assigned to
// `pxEndOfStack`, for stack alignment. So, if you passed in 200 words to
// `xTaskCreate()` (see: https://www.freertos.org/a00125.html) as the
// `usStackDepth` value, this function may return 199 instead, which is
// the actual, correct, stack size available.
// - Partially learned from:
// https://www.freertos.org/FreeRTOS_Support_Forum_Archive/January_2019/freertos_Retrieve_the_size_and_maximum_usage_of_the_stack_per_task_7ab5c6eb05j.html
uint32_t taskGetStackSizeWords(TaskHandle_t taskHandle)
{
uint32_t stackSizeBytes;
TCB_t * tcb = prvGetTCBFromHandle(taskHandle);
// critical section to access protected TCB (Task Control Block)
// members from this task's `struct tskTaskControlBlock`
taskENTER_CRITICAL();
{
// +1 to count both the end and start in the calculation
stackSizeBytes = tcb->pxEndOfStack - tcb->pxStack + 1;
}
taskEXIT_CRITICAL();
// Optionally [but not recommended], you may add +1 again to give
// us the same value that we input when we created the task;
// technically, `portBYTE_ALIGNMENT_MASK` removed one of our bytes
// due to alignment, so actually our stack is 1 byte less
// than our input at creation.
// stackSizeBytes += 1;
return stackSizeBytes;
}
// Get the stack size in bytes.
// - See details in the `taskGetStackSizeWords()` function above.
uint32_t taskGetStackSizeBytes(TaskHandle_t taskHandle)
{
uint32_t stackSizeWords =
taskGetStackSizeBytes(taskHandle)*sizeof(StackType_t);
return stackSizeWords;
}
#endif // ((configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1)
// && (configRECORD_STACK_HIGH_ADDRESS == 1))
Use the functions above.
In the source file where you want to use the functions above, you can get access to them by forward declaring them as follows, then using them afterwards:
#include "FreeRTOS.h"
#include "task.h"
// Note: `extern` here is optional for functions.
// See my answer here: https://stackoverflow.com/a/77527374/4561887
extern uint32_t taskGetStackSizeWords(TaskHandle_t taskHandle);
extern uint32_t taskGetStackSizeBytes(TaskHandle_t taskHandle);
void in_some_func()
{
uint32_t stackSizeBytes =
taskGetStackSizeBytes(xTaskGetCurrentTaskHandle());
uint32_t stackSizeWords =
taskGetStackSizeWords(xTaskGetCurrentTaskHandle());
// etc.
}
Or, even better, you can create a new header file with the forward declarations inside it. Ex:
freertos_task_c_additions_include.h
:
# pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "FreeRTOS.h"
#include "task.h"
#if ((configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1) \
&& (configRECORD_STACK_HIGH_ADDRESS == 1))
uint32_t taskGetStackSizeWords(TaskHandle_t taskHandle);
uint32_t taskGetStackSizeBytes(TaskHandle_t taskHandle);
#endif // ((configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1)
// && (configRECORD_STACK_HIGH_ADDRESS == 1))
#ifdef __cplusplus
}
#endif
Then use it:
#include "freertos_task_c_additions_include.h"
void in_some_func()
{
uint32_t stackSizeBytes =
taskGetStackSizeBytes(xTaskGetCurrentTaskHandle());
uint32_t stackSizeWords =
taskGetStackSizeWords(xTaskGetCurrentTaskHandle());
// etc.
}
Since you defined configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H
to 1
in your config file, FreeRTOS automatically includes this custom freertos_task_c_additions.h
file at the bottom of tasks.c
.
Treat this "header" file like a source code file that gets included in another source code file.
This way, your code in freertos_task_c_additions.h
can access all of the private members in tasks.c
. All of your customizations to FreeRTOS should be contained within this file, not inserted into the original FreeRTOS source code.
For more information:
configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H
.History.txt
file I quoted aboveextern "C"
to prevent name-mangling of C-compiled object files linked into C++ programs: What is the effect of extern "C"
in C++?vTaskList()
and vTaskListTasks()
: https://www.freertos.org/a00021.html#vTaskList