blob: bdde8025e001b54f01ff08734e90f48885c6697a [file] [log] [blame]
// 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