


07 Aug 2025

5 min read
Zephyr Thread Analyzer
-Sayooj K Karun

07 Aug 2025

5 min read
Zephyr Thread Analyzer
-Sayooj K Karun
Sayooj K Karun
Staff Engineer
Follow Us

In Embedded Systems, one of the biggest consumers of RAM is thread stacks. Guessing the right stack size for each thread is a dangerous game: too small, and you risk a catastrophic stack overflow; too large, and you're wasting memory that other parts of your application could use. So, how do you find that "just right" size?
Enter Zephyr's built-in Thread Analyzer. This powerful utility is your best friend for optimizing thread memory. It works using a clever technique called stack painting.
Here’s how it works: When a thread is created, Zephyr "paints" its entire allocated stack area with a known pattern of bytes. As the thread runs—calling functions, declaring local variables—it writes over this pattern. The Thread Analyzer can then check the stack at any time to see how much of the original "paint" is left. The highest point the stack pointer ever reached is known as the high watermark.
By periodically printing the analyzer's report, you get a clear, real-time snapshot of the maximum stack depth each thread has actually used during its runtime.
1
2
3
thread_analyzer: Thread work_thread: STACK: unused 592, used 432/1024 (42%)
This simple output tells you that work_thread, with its 1024-byte stack, has only used a maximum of 432 bytes so far. Armed with this concrete data, you can confidently trim the excess RAM allocation, add a reasonable safety buffer (say, 20-30%), and move on, knowing your thread is memory-efficient and stable. It transforms the risky guesswork of stack allocation into a precise, data-driven method.
Even with careful analysis, bugs happen. A runaway recursive function or an unexpected interrupt nesting could cause a thread to write past its allocated stack boundary, a bug that is notoriously difficult to track down. This is where Zephyr’s proactive protection mechanisms become your essential safety net. Think of them as guardrails for your thread's memory.
The first line of defense is the stack canary. The name comes from the old "canary in a coal mine" practice. When you enable this feature (CONFIG_STACK_CANARIES), the compiler places a known, secret value (the "canary") on the stack right before the function's return address. Before a function returns, it checks if this value is still intact. If a buffer overflow has occurred on the stack, it will have overwritten the canary. The check will fail, and the system can immediately halt or trigger an error handler, stopping the corruption before it spreads. It’s a simple but incredibly effective way to catch stack-based buffer overflows right at the source.
For even more robust protection, Zephyr can leverage the Memory Protection Unit (MPU) present on many ARM Cortex-M microcontrollers. By enabling CONFIG_HW_STACK_PROTECTION, you instruct the hardware itself to act as a security guard.
The MPU is configured to create a small, invalid memory region right at the end of each thread's stack. This region acts as a digital tripwire. If a thread's stack grows too large and attempts to write into this protected "guard" area, the MPU will instantly trigger a hardware fault. This stops the errant thread in its tracks, preventing it from corrupting adjacent memory belonging to other threads or the kernel. It’s the ultimate defense, as it catches any overflow, regardless of its cause, with zero software overhead during normal operation.
By using the Thread Analyzer to right-size your stacks and enabling these protection features as a fallback, you can build Zephyr applications that are not only memory-efficient but also fundamentally more robust and secure.
prj.conf
1
2
3
4
5
6
7
8
9
10
11
12
# Enable the thread analyzer and printk output
CONFIG_THREAD_ANALYZER=y
CONFIG_THREAD_ANALYZER_USE_PRINTK=y
# These are dependencies for the analyzer
CONFIG_INIT_STACKS=y
CONFIG_THREAD_NAME=y
CONFIG_PRINTK=y
# Set stack sizes for threads we want to monitor
CONFIG_MAIN_STACK_SIZE=1024
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(thread_analyzer)
target_sources(app PRIVATE src/main.c)
src/main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/debug/thread_analyzer.h>
// Define stack size and area for our custom thread
#define WORK_THREAD_STACK_SIZE 1024
K_THREAD_STACK_DEFINE(work_thread_stack_area, WORK_THREAD_STACK_SIZE);
// Define thread data structure
static struct k_thread work_thread_data;
// Define a work item
static struct k_work my_work;
/*
* This function is called recursively to simulate deep stack usage.
* Each call adds a small buffer to the stack.
*/
static void deepest_function(int depth) {
volatile char dummy_buffer[64];
if (depth > 0) {
deepest_function(depth - 1);
}
}
/*
* This is the handler for our work item. It runs on the system workqueue thread
* and calls the recursive function to consume stack space.
*/
static void work_handler(struct k_work *work) {
printk("Work handler running on thread %s
", k_thread_name_get(k_current_get()));
// Simulate a deep call stack to use more of the workqueue's stack
deepest_function(5);
printk("Work handler finished.
");
}
/*
* This is the entry point for our dedicated "work_thread".
*/
void work_thread_entry(void *p1, void *p2, void *p3) {
printk("Custom work_thread started.
");
// This local variable will consume stack space in this thread
volatile char local_buffer[384];
// Use the buffer to ensure the compiler doesn't optimize it away
for (int i = 0; i < sizeof(local_buffer); i++) {
local_buffer[i] = i;
}
printk("work_thread has used its stack and is now sleeping.
");
// This thread doesn't need to do anything else
k_sleep(K_FOREVER);
}
int main(void) {
printk("--- Zephyr Thread Analyzer Demo ---
");
// 1. Create a dedicated thread
k_tid_t work_tid = k_thread_create(&work_thread_data, work_thread_stack_area,
K_THREAD_STACK_SIZEOF(work_thread_stack_area),
work_thread_entry,
NULL, NULL, NULL,
K_PRIO_PREEMPT(7), 0, K_NO_WAIT);
k_thread_name_set(work_tid, "work_thread");
// 2. Initialize and submit a work item to the system workqueue
k_work_init(&my_work, work_handler);
k_work_submit(&my_work);
// 3. Periodically print the thread analyzer report
while (1) {
printk("
--- Analyzing all threads... ---
");
thread_analyzer_print(1);
k_sleep(K_SECONDS(5));
}
return 0;
}
Logs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
*** Booting Zephyr OS build 26512b06734f ***
--- Zephyr Thread Analyzer Demo ---
Work handler running on thread sysworkq
Work handler finished.
--- Analyzing all threads... ---
Thread analyze:
work_thread : STACK: unused 960 usage 64 / 1024 (6 %); CPU: 0 %
: Total CPU cycles used: 0
sysworkq : STACK: unused 1516 usage 532 / 2048 (25 %); CPU: 10 %
: Total CPU cycles used: 27140
idle : STACK: unused 448 usage 64 / 512 (12 %); CPU: 0 %
: Total CPU cycles used: 0
main : STACK: unused 268 usage 756 / 1024 (73 %); CPU: 95 %
: Total CPU cycles used: 594954
ISR0 : STACK: unused 0 usage 2048 / 2048 (100 %)
Custom work_thread started.
work_thread has used its stack and is now sleeping.
--- Analyzing all threads... ---
Thread analyze:
work_thread : STACK: unused 303 usage 721 / 1024 (70 %); CPU: 0 %
: Total CPU cycles used: 113767
sysworkq : STACK: unused 1516 usage 532 / 2048 (25 %); CPU: 0 %
: Total CPU cycles used: 27140
idle : STACK: unused 268 usage 244 / 512 (47 %); CPU: 98 %
: Total CPU cycles used: 79899940
main : STACK: unused 268 usage 756 / 1024 (73 %); CPU: 1 %
: Total CPU cycles used: 1306942
ISR0 : STACK: unused 0 usage 2048 / 2048 (100 %)
By using the Thread Analyzer to right-size your stacks, developers can transform stack allocation from a risky guessing game into a precise, data-driven method. This utility uses "stack painting" to measure the high watermark, showing the maximum stack depth each thread has used. As a crucial safety net, Zephyr's proactive protection mechanisms, such as Stack Canaries and Hardware Stack Protection, prevent catastrophic stack overflows. Stack canaries catch buffer overflows by checking a secret value placed on the stack before a function returns , while hardware protection leverages the MPU to create a "digital tripwire" that triggers a fault if a thread attempts to write past its allocated stack boundary.
Combining these features allows you to build Zephyr applications that are not only memory-efficient but also fundamentally more robust and secure. Have a business idea and want to convert it into a Zephyr OS based product? Want to know more about Aerlync’s expertise in Zephyr RTOS? Contact Us!

In Embedded Systems, one of the biggest consumers of RAM is thread stacks. Guessing the right stack size for each thread is a dangerous game: too small, and you risk a catastrophic stack overflow; too large, and you're wasting memory that other parts of your application could use. So, how do you find that "just right" size?
Enter Zephyr's built-in Thread Analyzer. This powerful utility is your best friend for optimizing thread memory. It works using a clever technique called stack painting.
Here’s how it works: When a thread is created, Zephyr "paints" its entire allocated stack area with a known pattern of bytes. As the thread runs—calling functions, declaring local variables—it writes over this pattern. The Thread Analyzer can then check the stack at any time to see how much of the original "paint" is left. The highest point the stack pointer ever reached is known as the high watermark.
By periodically printing the analyzer's report, you get a clear, real-time snapshot of the maximum stack depth each thread has actually used during its runtime.
1
2
3
thread_analyzer: Thread work_thread: STACK: unused 592, used 432/1024 (42%)
This simple output tells you that work_thread, with its 1024-byte stack, has only used a maximum of 432 bytes so far. Armed with this concrete data, you can confidently trim the excess RAM allocation, add a reasonable safety buffer (say, 20-30%), and move on, knowing your thread is memory-efficient and stable. It transforms the risky guesswork of stack allocation into a precise, data-driven method.
Even with careful analysis, bugs happen. A runaway recursive function or an unexpected interrupt nesting could cause a thread to write past its allocated stack boundary, a bug that is notoriously difficult to track down. This is where Zephyr’s proactive protection mechanisms become your essential safety net. Think of them as guardrails for your thread's memory.
The first line of defense is the stack canary. The name comes from the old "canary in a coal mine" practice. When you enable this feature (CONFIG_STACK_CANARIES), the compiler places a known, secret value (the "canary") on the stack right before the function's return address. Before a function returns, it checks if this value is still intact. If a buffer overflow has occurred on the stack, it will have overwritten the canary. The check will fail, and the system can immediately halt or trigger an error handler, stopping the corruption before it spreads. It’s a simple but incredibly effective way to catch stack-based buffer overflows right at the source.
For even more robust protection, Zephyr can leverage the Memory Protection Unit (MPU) present on many ARM Cortex-M microcontrollers. By enabling CONFIG_HW_STACK_PROTECTION, you instruct the hardware itself to act as a security guard.
The MPU is configured to create a small, invalid memory region right at the end of each thread's stack. This region acts as a digital tripwire. If a thread's stack grows too large and attempts to write into this protected "guard" area, the MPU will instantly trigger a hardware fault. This stops the errant thread in its tracks, preventing it from corrupting adjacent memory belonging to other threads or the kernel. It’s the ultimate defense, as it catches any overflow, regardless of its cause, with zero software overhead during normal operation.
By using the Thread Analyzer to right-size your stacks and enabling these protection features as a fallback, you can build Zephyr applications that are not only memory-efficient but also fundamentally more robust and secure.
prj.conf
1
2
3
4
5
6
7
8
9
10
11
12
# Enable the thread analyzer and printk output
CONFIG_THREAD_ANALYZER=y
CONFIG_THREAD_ANALYZER_USE_PRINTK=y
# These are dependencies for the analyzer
CONFIG_INIT_STACKS=y
CONFIG_THREAD_NAME=y
CONFIG_PRINTK=y
# Set stack sizes for threads we want to monitor
CONFIG_MAIN_STACK_SIZE=1024
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(thread_analyzer)
target_sources(app PRIVATE src/main.c)
src/main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/debug/thread_analyzer.h>
// Define stack size and area for our custom thread
#define WORK_THREAD_STACK_SIZE 1024
K_THREAD_STACK_DEFINE(work_thread_stack_area, WORK_THREAD_STACK_SIZE);
// Define thread data structure
static struct k_thread work_thread_data;
// Define a work item
static struct k_work my_work;
/*
* This function is called recursively to simulate deep stack usage.
* Each call adds a small buffer to the stack.
*/
static void deepest_function(int depth) {
volatile char dummy_buffer[64];
if (depth > 0) {
deepest_function(depth - 1);
}
}
/*
* This is the handler for our work item. It runs on the system workqueue thread
* and calls the recursive function to consume stack space.
*/
static void work_handler(struct k_work *work) {
printk("Work handler running on thread %s
", k_thread_name_get(k_current_get()));
// Simulate a deep call stack to use more of the workqueue's stack
deepest_function(5);
printk("Work handler finished.
");
}
/*
* This is the entry point for our dedicated "work_thread".
*/
void work_thread_entry(void *p1, void *p2, void *p3) {
printk("Custom work_thread started.
");
// This local variable will consume stack space in this thread
volatile char local_buffer[384];
// Use the buffer to ensure the compiler doesn't optimize it away
for (int i = 0; i < sizeof(local_buffer); i++) {
local_buffer[i] = i;
}
printk("work_thread has used its stack and is now sleeping.
");
// This thread doesn't need to do anything else
k_sleep(K_FOREVER);
}
int main(void) {
printk("--- Zephyr Thread Analyzer Demo ---
");
// 1. Create a dedicated thread
k_tid_t work_tid = k_thread_create(&work_thread_data, work_thread_stack_area,
K_THREAD_STACK_SIZEOF(work_thread_stack_area),
work_thread_entry,
NULL, NULL, NULL,
K_PRIO_PREEMPT(7), 0, K_NO_WAIT);
k_thread_name_set(work_tid, "work_thread");
// 2. Initialize and submit a work item to the system workqueue
k_work_init(&my_work, work_handler);
k_work_submit(&my_work);
// 3. Periodically print the thread analyzer report
while (1) {
printk("
--- Analyzing all threads... ---
");
thread_analyzer_print(1);
k_sleep(K_SECONDS(5));
}
return 0;
}
Logs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
*** Booting Zephyr OS build 26512b06734f ***
--- Zephyr Thread Analyzer Demo ---
Work handler running on thread sysworkq
Work handler finished.
--- Analyzing all threads... ---
Thread analyze:
work_thread : STACK: unused 960 usage 64 / 1024 (6 %); CPU: 0 %
: Total CPU cycles used: 0
sysworkq : STACK: unused 1516 usage 532 / 2048 (25 %); CPU: 10 %
: Total CPU cycles used: 27140
idle : STACK: unused 448 usage 64 / 512 (12 %); CPU: 0 %
: Total CPU cycles used: 0
main : STACK: unused 268 usage 756 / 1024 (73 %); CPU: 95 %
: Total CPU cycles used: 594954
ISR0 : STACK: unused 0 usage 2048 / 2048 (100 %)
Custom work_thread started.
work_thread has used its stack and is now sleeping.
--- Analyzing all threads... ---
Thread analyze:
work_thread : STACK: unused 303 usage 721 / 1024 (70 %); CPU: 0 %
: Total CPU cycles used: 113767
sysworkq : STACK: unused 1516 usage 532 / 2048 (25 %); CPU: 0 %
: Total CPU cycles used: 27140
idle : STACK: unused 268 usage 244 / 512 (47 %); CPU: 98 %
: Total CPU cycles used: 79899940
main : STACK: unused 268 usage 756 / 1024 (73 %); CPU: 1 %
: Total CPU cycles used: 1306942
ISR0 : STACK: unused 0 usage 2048 / 2048 (100 %)
By using the Thread Analyzer to right-size your stacks, developers can transform stack allocation from a risky guessing game into a precise, data-driven method. This utility uses "stack painting" to measure the high watermark, showing the maximum stack depth each thread has used. As a crucial safety net, Zephyr's proactive protection mechanisms, such as Stack Canaries and Hardware Stack Protection, prevent catastrophic stack overflows. Stack canaries catch buffer overflows by checking a secret value placed on the stack before a function returns , while hardware protection leverages the MPU to create a "digital tripwire" that triggers a fault if a thread attempts to write past its allocated stack boundary.
Combining these features allows you to build Zephyr applications that are not only memory-efficient but also fundamentally more robust and secure. Have a business idea and want to convert it into a Zephyr OS based product? Want to know more about Aerlync’s expertise in Zephyr RTOS? Contact Us!
Build with the Most Trusted Engineering Partner
Delivers cutting-edge embedded solutions, from firmware development to wireless protocols, ensuring reliability and innovation.
Copyright © 2026
Privacy Policy
Terms of Service

Delivers cutting-edge embedded solutions, from firmware development to wireless protocols, ensuring reliability and innovation.
Privacy Policy
Terms of Service
Copyright © 2026