xref: /aosp_15_r20/external/pigweed/pw_thread_threadx/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_THREADX_CONFIG_LOG_LEVEL
16 
17 #include "pw_thread_threadx/snapshot.h"
18 
19 #include <string_view>
20 
21 #include "pw_function/function.h"
22 #include "pw_log/log.h"
23 #include "pw_protobuf/encoder.h"
24 #include "pw_status/status.h"
25 #include "pw_thread/snapshot.h"
26 #include "pw_thread_protos/thread.pwpb.h"
27 #include "pw_thread_threadx/config.h"
28 #include "pw_thread_threadx/util.h"
29 #include "tx_api.h"
30 #include "tx_thread.h"
31 
32 namespace pw::thread::threadx {
33 namespace {
34 
35 // TODO(amontanez): This might make unit testing codepaths that use this more
36 // challenging.
ThreadIsRunning(const TX_THREAD & thread)37 inline bool ThreadIsRunning(const TX_THREAD& thread) {
38   const TX_THREAD* running_thread;
39   TX_THREAD_GET_CURRENT(running_thread);
40   return running_thread == &thread;
41 }
42 
CaptureThreadState(const TX_THREAD & thread,proto::Thread::StreamEncoder & encoder)43 void CaptureThreadState(const TX_THREAD& thread,
44                         proto::Thread::StreamEncoder& encoder) {
45   if (ThreadIsRunning(thread)) {
46     PW_LOG_DEBUG("Thread state: RUNNING");
47     encoder.WriteState(proto::ThreadState::Enum::RUNNING);
48     return;
49   }
50 
51   switch (thread.tx_thread_state) {
52     case TX_READY:
53       PW_LOG_DEBUG("Thread state: READY");
54       encoder.WriteState(proto::ThreadState::Enum::READY);
55       break;
56     case TX_COMPLETED:
57     case TX_TERMINATED:
58       PW_LOG_DEBUG("Thread state: INACTIVE");
59       encoder.WriteState(proto::ThreadState::Enum::INACTIVE);
60       break;
61     case TX_SUSPENDED:
62     case TX_SLEEP:
63       PW_LOG_DEBUG("Thread state: SUSPENDED");
64       encoder.WriteState(proto::ThreadState::Enum::SUSPENDED);
65       break;
66     case TX_QUEUE_SUSP:
67     case TX_SEMAPHORE_SUSP:
68     case TX_EVENT_FLAG:
69     case TX_BLOCK_MEMORY:
70     case TX_BYTE_MEMORY:
71     case TX_IO_DRIVER:
72     case TX_FILE:
73     case TX_TCP_IP:
74     case TX_MUTEX_SUSP:
75       PW_LOG_DEBUG("Thread state: BLOCKED");
76       encoder.WriteState(proto::ThreadState::Enum::BLOCKED);
77       break;
78     default:
79       PW_LOG_DEBUG("Thread state: UNKNOWN");
80       encoder.WriteState(proto::ThreadState::Enum::UNKNOWN);
81   }
82 }
83 
84 }  // namespace
85 
SnapshotThreads(void * running_thread_stack_pointer,proto::SnapshotThreadInfo::StreamEncoder & encoder,ProcessThreadStackCallback & stack_dumper)86 Status SnapshotThreads(void* running_thread_stack_pointer,
87                        proto::SnapshotThreadInfo::StreamEncoder& encoder,
88                        ProcessThreadStackCallback& stack_dumper) {
89   struct {
90     void* running_thread_stack_pointer;
91     proto::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 
99   ThreadCallback thread_capture_cb([&ctx](const TX_THREAD& thread) -> bool {
100     proto::Thread::StreamEncoder thread_encoder =
101         ctx.encoder->GetThreadsEncoder();
102     ctx.thread_capture_status.Update(
103         SnapshotThread(thread,
104                        ctx.running_thread_stack_pointer,
105                        thread_encoder,
106                        *ctx.stack_dumper));
107     // Always iterate all threads.
108     return true;
109   });
110 
111   if (Status status = ForEachThread(thread_capture_cb); !status.ok()) {
112     PW_LOG_ERROR("Failed to iterate threads during snapshot capture: %d",
113                  static_cast<int>(status.code()));
114   }
115 
116   return ctx.thread_capture_status;
117 }
118 
SnapshotThread(const TX_THREAD & thread,void * running_thread_stack_pointer,proto::Thread::StreamEncoder & encoder,ProcessThreadStackCallback & thread_stack_callback)119 Status SnapshotThread(const TX_THREAD& thread,
120                       void* running_thread_stack_pointer,
121                       proto::Thread::StreamEncoder& encoder,
122                       ProcessThreadStackCallback& thread_stack_callback) {
123   PW_LOG_DEBUG("Capturing thread info for %s", thread.tx_thread_name);
124   encoder.WriteName(as_bytes(span(std::string_view(thread.tx_thread_name))));
125 
126   CaptureThreadState(thread, encoder);
127 
128   const StackContext thread_ctx = {
129       .thread_name = thread.tx_thread_name,
130 
131       // TODO(amontanez): When ThreadX is built with stack checking enabled, the
132       // lowest-addressed `unsigned long` is reserved for a watermark. This
133       // means in practice the stack pointer should never end up there. To be
134       // conservative, behave as though TX_THREAD_STACK_CHECK is always fully
135       // enabled.
136       .stack_low_addr =
137           reinterpret_cast<uintptr_t>(thread.tx_thread_stack_start) +
138           sizeof(ULONG),
139 
140       .stack_high_addr =
141           reinterpret_cast<uintptr_t>(thread.tx_thread_stack_end),
142 
143       // If the thread is active, the stack pointer in the TCB is stale.
144       .stack_pointer = reinterpret_cast<uintptr_t>(
145           ThreadIsRunning(thread) ? running_thread_stack_pointer
146                                   : thread.tx_thread_stack_ptr),
147       .stack_pointer_est_peak = std::nullopt,
148   };
149 
150   return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
151 }
152 
153 }  // namespace pw::thread::threadx
154