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_EMBOS_CONFIG_LOG_LEVEL
16
17 #include "pw_thread_embos/snapshot.h"
18
19 #include <string_view>
20
21 #include "RTOS.h"
22 #include "pw_function/function.h"
23 #include "pw_log/log.h"
24 #include "pw_protobuf/encoder.h"
25 #include "pw_status/status.h"
26 #include "pw_thread/snapshot.h"
27 #include "pw_thread_embos/config.h"
28 #include "pw_thread_embos/util.h"
29 #include "pw_thread_protos/thread.pwpb.h"
30
31 namespace pw::thread::embos {
32 namespace {
33
34 // TODO(amontanez): This might make unit testing codepaths that use this more
35 // challenging.
ThreadIsRunning(const OS_TASK & thread)36 inline bool ThreadIsRunning(const OS_TASK& thread) {
37 return OS_GetpCurrentTask() == &thread;
38 }
39
CaptureThreadState(const OS_TASK & thread,proto::Thread::StreamEncoder & encoder)40 void CaptureThreadState(const OS_TASK& thread,
41 proto::Thread::StreamEncoder& encoder) {
42 if (ThreadIsRunning(thread)) {
43 PW_LOG_DEBUG("Thread state: RUNNING");
44 encoder.WriteState(proto::ThreadState::Enum::RUNNING);
45 return;
46 }
47
48 // One byte is reserved for task status.
49 // - The lowest two bits are for a suspend counter.
50 // - The third-lowest bit is reserved for a "timeout." (ignored here)
51 // - The highest five bits indicate what the task is blocked on if non-zero.
52 //
53 // Note: embOS thread state is not part of the public API. This may not be
54 // correct for all versions. This has been tested on embOS 4.22, and was
55 // initially reported for embOS 5.06.
56 //
57 // Description of how `OS_TASK::Stat` is used by embOS:
58 // https://forum.segger.com/index.php/Thread/6548-ABANDONED-Task-state-values/?postID=23963#post23963
59 #if OS_VERSION_GENERIC < 42200 || OS_VERSION_GENERIC > 50600
60 #warning embOS thread state interpretation logic is not verfied as working on this version of embOS
61 #endif // OS_VERSION_GENERIC < 42200 || OS_VERSION_GENERIC > 50600
62
63 if ((thread.Stat & 0x3) != 0) {
64 PW_LOG_DEBUG("Thread state: SUSPENDED");
65 encoder.WriteState(proto::ThreadState::Enum::SUSPENDED);
66 } else if ((thread.Stat & 0xf8) == 0) {
67 PW_LOG_DEBUG("Thread state: READY");
68 encoder.WriteState(proto::ThreadState::Enum::READY);
69 } else {
70 PW_LOG_DEBUG("Thread state: BLOCKED");
71 encoder.WriteState(proto::ThreadState::Enum::BLOCKED);
72 }
73 }
74
75 } // namespace
76
SnapshotThreads(void * running_thread_stack_pointer,proto::SnapshotThreadInfo::StreamEncoder & encoder,ProcessThreadStackCallback & stack_dumper)77 Status SnapshotThreads(void* running_thread_stack_pointer,
78 proto::SnapshotThreadInfo::StreamEncoder& encoder,
79 ProcessThreadStackCallback& stack_dumper) {
80 struct {
81 void* running_thread_stack_pointer;
82 proto::SnapshotThreadInfo::StreamEncoder* encoder;
83 ProcessThreadStackCallback* stack_dumper;
84 Status thread_capture_status;
85 } ctx;
86 ctx.running_thread_stack_pointer = running_thread_stack_pointer;
87 ctx.encoder = &encoder;
88 ctx.stack_dumper = &stack_dumper;
89
90 ThreadCallback thread_capture_cb([&ctx](const OS_TASK& thread) -> bool {
91 proto::Thread::StreamEncoder thread_encoder =
92 ctx.encoder->GetThreadsEncoder();
93 ctx.thread_capture_status.Update(
94 SnapshotThread(thread,
95 ctx.running_thread_stack_pointer,
96 thread_encoder,
97 *ctx.stack_dumper));
98 // Always iterate all threads.
99 return true;
100 });
101
102 if (Status status = ForEachThread(thread_capture_cb);
103 !status.ok() && !status.IsFailedPrecondition()) {
104 PW_LOG_ERROR("Failed to iterate threads during snapshot capture: %d",
105 static_cast<int>(status.code()));
106 }
107
108 return ctx.thread_capture_status;
109 }
110
SnapshotThread(const OS_TASK & thread,void * running_thread_stack_pointer,proto::Thread::StreamEncoder & encoder,ProcessThreadStackCallback & thread_stack_callback)111 Status SnapshotThread(const OS_TASK& thread,
112 void* running_thread_stack_pointer,
113 proto::Thread::StreamEncoder& encoder,
114 ProcessThreadStackCallback& thread_stack_callback) {
115 #if OS_TRACKNAME
116 PW_LOG_DEBUG("Capturing thread info for %s", thread.Name);
117 encoder.WriteName(as_bytes(span(std::string_view(thread.Name))));
118 #else
119 PW_LOG_DEBUG("Capturing thread info for thread at 0x%08x", &thread);
120 #endif // OS_TRACKNAME
121
122 CaptureThreadState(thread, encoder);
123
124 #if OS_CHECKSTACK || OS_SUPPORT_MPU
125 const StackContext thread_ctx = {
126 .thread_name = thread.Name,
127
128 .stack_low_addr = reinterpret_cast<uintptr_t>(thread.pStackBot),
129
130 .stack_high_addr =
131 reinterpret_cast<uintptr_t>(thread.pStackBot) + thread.StackSize,
132
133 // If the thread is active, the stack pointer in the TCB is stale.
134 .stack_pointer = reinterpret_cast<uintptr_t>(
135 ThreadIsRunning(thread) ? running_thread_stack_pointer
136 : thread.pStack),
137 .stack_pointer_est_peak = reinterpret_cast<uintptr_t>(thread.pStackBot) +
138 thread.StackSize - OS_GetStackUsed(&thread),
139 };
140
141 return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
142 #else
143 PW_LOG_DEBUG("Stack pointer: 0x%08x", running_thread_stack_pointer);
144 encoder.WriteStackPointer(reinterpret_cast<uintptr_t>(
145 ThreadIsRunning(thread) ? running_thread_stack_pointer : thread.pStack));
146 return encoder.status();
147 #endif // OS_CHECKSTACK || OS_SUPPORT_MPU
148 }
149
150 } // namespace pw::thread::embos
151