| // Copyright 2021 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #define PW_LOG_LEVEL PW_THREAD_THREADX_CONFIG_LOG_LEVEL |
| |
| #include "pw_thread_threadx/snapshot.h" |
| |
| #include <string_view> |
| |
| #include "pw_function/function.h" |
| #include "pw_log/log.h" |
| #include "pw_protobuf/encoder.h" |
| #include "pw_status/status.h" |
| #include "pw_thread/snapshot.h" |
| #include "pw_thread_protos/thread.pwpb.h" |
| #include "pw_thread_threadx/config.h" |
| #include "pw_thread_threadx/util.h" |
| #include "tx_api.h" |
| #include "tx_thread.h" |
| |
| namespace pw::thread::threadx { |
| namespace { |
| |
| // TODO(amontanez): This might make unit testing codepaths that use this more |
| // challenging. |
| inline bool ThreadIsRunning(const TX_THREAD& thread) { |
| const TX_THREAD* running_thread; |
| TX_THREAD_GET_CURRENT(running_thread); |
| return running_thread == &thread; |
| } |
| |
| void CaptureThreadState(const TX_THREAD& thread, |
| Thread::StreamEncoder& encoder) { |
| if (ThreadIsRunning(thread)) { |
| PW_LOG_DEBUG("Thread state: RUNNING"); |
| encoder.WriteState(ThreadState::Enum::RUNNING); |
| return; |
| } |
| |
| switch (thread.tx_thread_state) { |
| case TX_READY: |
| PW_LOG_DEBUG("Thread state: READY"); |
| encoder.WriteState(ThreadState::Enum::READY); |
| break; |
| case TX_COMPLETED: |
| case TX_TERMINATED: |
| PW_LOG_DEBUG("Thread state: INACTIVE"); |
| encoder.WriteState(ThreadState::Enum::INACTIVE); |
| break; |
| case TX_SUSPENDED: |
| case TX_SLEEP: |
| PW_LOG_DEBUG("Thread state: SUSPENDED"); |
| encoder.WriteState(ThreadState::Enum::SUSPENDED); |
| break; |
| case TX_QUEUE_SUSP: |
| case TX_SEMAPHORE_SUSP: |
| case TX_EVENT_FLAG: |
| case TX_BLOCK_MEMORY: |
| case TX_BYTE_MEMORY: |
| case TX_IO_DRIVER: |
| case TX_FILE: |
| case TX_TCP_IP: |
| case TX_MUTEX_SUSP: |
| PW_LOG_DEBUG("Thread state: BLOCKED"); |
| encoder.WriteState(ThreadState::Enum::BLOCKED); |
| break; |
| default: |
| PW_LOG_DEBUG("Thread state: UNKNOWN"); |
| encoder.WriteState(ThreadState::Enum::UNKNOWN); |
| } |
| } |
| |
| } // namespace |
| |
| Status SnapshotThreads(void* running_thread_stack_pointer, |
| SnapshotThreadInfo::StreamEncoder& encoder, |
| ProcessThreadStackCallback& stack_dumper) { |
| struct { |
| void* running_thread_stack_pointer; |
| SnapshotThreadInfo::StreamEncoder* encoder; |
| ProcessThreadStackCallback* stack_dumper; |
| Status thread_capture_status; |
| } ctx; |
| ctx.running_thread_stack_pointer = running_thread_stack_pointer; |
| ctx.encoder = &encoder; |
| ctx.stack_dumper = &stack_dumper; |
| |
| ThreadCallback thread_capture_cb([&ctx](const TX_THREAD& thread) -> bool { |
| Thread::StreamEncoder thread_encoder = ctx.encoder->GetThreadsEncoder(); |
| ctx.thread_capture_status.Update( |
| SnapshotThread(thread, |
| ctx.running_thread_stack_pointer, |
| thread_encoder, |
| *ctx.stack_dumper)); |
| // Always iterate all threads. |
| return true; |
| }); |
| |
| if (Status status = ForEachThread(thread_capture_cb); !status.ok()) { |
| PW_LOG_ERROR("Failed to iterate threads during snapshot capture: %d", |
| static_cast<int>(status.code())); |
| } |
| |
| return ctx.thread_capture_status; |
| } |
| |
| Status SnapshotThread(const TX_THREAD& thread, |
| void* running_thread_stack_pointer, |
| Thread::StreamEncoder& encoder, |
| ProcessThreadStackCallback& thread_stack_callback) { |
| PW_LOG_DEBUG("Capturing thread info for %s", thread.tx_thread_name); |
| encoder.WriteName(as_bytes(span(std::string_view(thread.tx_thread_name)))); |
| |
| CaptureThreadState(thread, encoder); |
| |
| const StackContext thread_ctx = { |
| .thread_name = thread.tx_thread_name, |
| |
| // TODO(amontanez): When ThreadX is built with stack checking enabled, the |
| // lowest-addressed `unsigned long` is reserved for a watermark. This |
| // means in practice the stack pointer should never end up there. To be |
| // conservative, behave as though TX_THREAD_STACK_CHECK is always fully |
| // enabled. |
| .stack_low_addr = |
| reinterpret_cast<uintptr_t>(thread.tx_thread_stack_start) + |
| sizeof(ULONG), |
| |
| .stack_high_addr = |
| reinterpret_cast<uintptr_t>(thread.tx_thread_stack_end), |
| |
| // If the thread is active, the stack pointer in the TCB is stale. |
| .stack_pointer = reinterpret_cast<uintptr_t>( |
| ThreadIsRunning(thread) ? running_thread_stack_pointer |
| : thread.tx_thread_stack_ptr), |
| .stack_pointer_est_peak = std::nullopt, |
| }; |
| |
| return SnapshotStack(thread_ctx, encoder, thread_stack_callback); |
| } |
| |
| } // namespace pw::thread::threadx |