xref: /aosp_15_r20/external/pigweed/pw_thread_freertos/snapshot.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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