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