1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #define PW_LOG_LEVEL PW_THREAD_FREERTOS_CONFIG_LOG_LEVEL
16
17 #include "pw_thread_freertos/snapshot.h"
18
19 #include <string_view>
20
21 #include "FreeRTOS.h"
22 #include "pw_function/function.h"
23 #include "pw_log/log.h"
24 #include "pw_protobuf/encoder.h"
25 #include "pw_span/span.h"
26 #include "pw_status/status.h"
27 #include "pw_thread/snapshot.h"
28 #include "pw_thread_freertos/config.h"
29 #include "pw_thread_freertos/freertos_tsktcb.h"
30 #include "pw_thread_freertos/util.h"
31 #include "pw_thread_protos/thread.pwpb.h"
32 #include "task.h"
33
34 namespace pw::thread::freertos {
35 namespace {
36
37 // The externed function is an internal FreeRTOS kernel function from
38 // FreeRTOS/Source/tasks.c needed in order to calculate a thread's stack usage
39 // from interrupts which the native APIs do not permit.
40 #if ((configUSE_TRACE_FACILITY == 1) || \
41 (INCLUDE_uxTaskGetStackHighWaterMark == 1))
42 extern "C" uint16_t prvTaskCheckFreeStackSpace(const uint8_t* pucStackByte);
43 #endif // ((configUSE_TRACE_FACILITY == 1) ||
44 // (INCLUDE_uxTaskGetStackHighWaterMark == 1))
45
CaptureThreadState(eTaskState thread_state,proto::pwpb::Thread::StreamEncoder & encoder)46 void CaptureThreadState(eTaskState thread_state,
47 proto::pwpb::Thread::StreamEncoder& encoder) {
48 switch (thread_state) {
49 case eRunning:
50 PW_LOG_DEBUG("Thread state: RUNNING");
51 encoder.WriteState(proto::pwpb::ThreadState::Enum::RUNNING).IgnoreError();
52 return;
53
54 case eReady:
55 PW_LOG_DEBUG("Thread state: READY");
56 encoder.WriteState(proto::pwpb::ThreadState::Enum::READY).IgnoreError();
57 return;
58
59 case eBlocked:
60 PW_LOG_DEBUG("Thread state: BLOCKED");
61 encoder.WriteState(proto::pwpb::ThreadState::Enum::BLOCKED).IgnoreError();
62 return;
63
64 case eSuspended:
65 PW_LOG_DEBUG("Thread state: SUSPENDED");
66 encoder.WriteState(proto::pwpb::ThreadState::Enum::SUSPENDED)
67 .IgnoreError();
68 return;
69
70 case eDeleted:
71 PW_LOG_DEBUG("Thread state: INACTIVE");
72 encoder.WriteState(proto::pwpb::ThreadState::Enum::INACTIVE)
73 .IgnoreError();
74 return;
75
76 case eInvalid:
77 default:
78 PW_LOG_DEBUG("Thread state: UNKNOWN");
79 encoder.WriteState(proto::pwpb::ThreadState::Enum::UNKNOWN).IgnoreError();
80 return;
81 }
82 }
83
84 } // namespace
85
SnapshotThreads(void * running_thread_stack_pointer,proto::pwpb::SnapshotThreadInfo::StreamEncoder & encoder,ProcessThreadStackCallback & stack_dumper)86 Status SnapshotThreads(void* running_thread_stack_pointer,
87 proto::pwpb::SnapshotThreadInfo::StreamEncoder& encoder,
88 ProcessThreadStackCallback& stack_dumper) {
89 struct {
90 void* running_thread_stack_pointer;
91 proto::pwpb::SnapshotThreadInfo::StreamEncoder* encoder;
92 ProcessThreadStackCallback* stack_dumper;
93 Status thread_capture_status;
94 } ctx;
95 ctx.running_thread_stack_pointer = running_thread_stack_pointer;
96 ctx.encoder = &encoder;
97 ctx.stack_dumper = &stack_dumper;
98 ctx.thread_capture_status = OkStatus();
99
100 ThreadCallback thread_capture_cb(
101 [&ctx](TaskHandle_t thread, eTaskState thread_state) -> bool {
102 proto::pwpb::Thread::StreamEncoder thread_encoder =
103 ctx.encoder->GetThreadsEncoder();
104 ctx.thread_capture_status.Update(
105 SnapshotThread(thread,
106 thread_state,
107 ctx.running_thread_stack_pointer,
108 thread_encoder,
109 *ctx.stack_dumper));
110 return true; // Iterate through all threads.
111 });
112 if (const Status status = ForEachThread(thread_capture_cb);
113 !status.ok() && !status.IsFailedPrecondition()) {
114 PW_LOG_ERROR("Failed to iterate threads during snapshot capture: %d",
115 status.code());
116 }
117 return ctx.thread_capture_status;
118 }
119
SnapshotThread(TaskHandle_t thread,eTaskState thread_state,void * running_thread_stack_pointer,proto::pwpb::Thread::StreamEncoder & encoder,ProcessThreadStackCallback & thread_stack_callback)120 Status SnapshotThread(
121 TaskHandle_t thread,
122 eTaskState thread_state,
123 void* running_thread_stack_pointer,
124 proto::pwpb::Thread::StreamEncoder& encoder,
125 [[maybe_unused]] ProcessThreadStackCallback& thread_stack_callback) {
126 const tskTCB& tcb = *reinterpret_cast<tskTCB*>(thread);
127
128 PW_LOG_DEBUG("Capturing thread info for %s", tcb.pcTaskName);
129 PW_TRY(encoder.WriteName(as_bytes(span(std::string_view(tcb.pcTaskName)))));
130
131 CaptureThreadState(thread_state, encoder);
132
133 // TODO: b/234890430 - Update this once we add support for ascending stacks.
134 static_assert(portSTACK_GROWTH < 0, "Ascending stacks are not yet supported");
135
136 // If running_thread_stack_pointer is null, always use the stack pointer
137 // stored to the TCB.
138 bool use_running_thread_stack_pointer =
139 (thread_state == eRunning && running_thread_stack_pointer != nullptr);
140
141 const uintptr_t stack_pointer = reinterpret_cast<uintptr_t>(
142 use_running_thread_stack_pointer ? running_thread_stack_pointer
143 : tcb.pxTopOfStack);
144 const uintptr_t stack_low_addr = reinterpret_cast<uintptr_t>(tcb.pxStack);
145
146 #if ((portSTACK_GROWTH > 0) || (configRECORD_STACK_HIGH_ADDRESS == 1))
147 const uintptr_t stack_high_addr =
148 reinterpret_cast<uintptr_t>(tcb.pxEndOfStack);
149 const StackContext thread_ctx = {
150 .thread_name = tcb.pcTaskName,
151 .stack_low_addr = stack_low_addr,
152 .stack_high_addr = stack_high_addr,
153 .stack_pointer = stack_pointer,
154 #if ((configUSE_TRACE_FACILITY == 1) || \
155 (INCLUDE_uxTaskGetStackHighWaterMark == 1))
156 #if (portSTACK_GROWTH > 0)
157 .stack_pointer_est_peak =
158 stack_high_addr -
159 (sizeof(StackType_t) *
160 prvTaskCheckFreeStackSpace(
161 reinterpret_cast<const uint8_t*>(stack_high_addr))),
162 #else
163 .stack_pointer_est_peak =
164 stack_low_addr +
165 (sizeof(StackType_t) *
166 prvTaskCheckFreeStackSpace(
167 reinterpret_cast<const uint8_t*>(stack_low_addr))),
168 #endif // (portSTACK_GROWTH > 0)
169 #else
170 .stack_pointer_est_peak = std::nullopt,
171 #endif // ((configUSE_TRACE_FACILITY == 1) ||
172 // (INCLUDE_uxTaskGetStackHighWaterMark == 1))
173 };
174 return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
175 #else
176 encoder.WriteStackEndPointer(stack_low_addr);
177 encoder.WriteStackPointer(stack_pointer);
178 return encoder.status();
179 #endif // ((portSTACK_GROWTH > 0) || (configRECORD_STACK_HIGH_ADDRESS == 1))
180 }
181
182 } // namespace pw::thread::freertos
183