149 lines
5.4 KiB
C++
149 lines
5.4 KiB
C++
// 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_EMBOS_CONFIG_LOG_LEVEL
|
|
|
|
#include "pw_thread_embos/snapshot.h"
|
|
|
|
#include <string_view>
|
|
|
|
#include "RTOS.h"
|
|
#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_embos/config.h"
|
|
#include "pw_thread_embos/util.h"
|
|
#include "pw_thread_protos/thread.pwpb.h"
|
|
|
|
namespace pw::thread::embos {
|
|
namespace {
|
|
|
|
// TODO(amontanez): This might make unit testing codepaths that use this more
|
|
// challenging.
|
|
inline bool ThreadIsRunning(const OS_TASK& thread) {
|
|
return OS_GetpCurrentTask() == &thread;
|
|
}
|
|
|
|
void CaptureThreadState(const OS_TASK& thread, Thread::StreamEncoder& encoder) {
|
|
if (ThreadIsRunning(thread)) {
|
|
PW_LOG_DEBUG("Thread state: RUNNING");
|
|
encoder.WriteState(ThreadState::Enum::RUNNING);
|
|
return;
|
|
}
|
|
|
|
// One byte is reserved for task status.
|
|
// - The lowest two bits are for a suspend counter.
|
|
// - The third-lowest bit is reserved for a "timeout." (ignored here)
|
|
// - The highest five bits indicate what the task is blocked on if non-zero.
|
|
//
|
|
// Note: embOS thread state is not part of the public API. This may not be
|
|
// correct for all versions. This has been tested on embOS 4.22, and was
|
|
// initially reported for embOS 5.06.
|
|
//
|
|
// Description of how `OS_TASK::Stat` is used by embOS:
|
|
// https://forum.segger.com/index.php/Thread/6548-ABANDONED-Task-state-values/?postID=23963#post23963
|
|
#if OS_VERSION_GENERIC < 42200 || OS_VERSION_GENERIC > 50600
|
|
#warning embOS thread state interpretation logic is not verfied as working on this version of embOS
|
|
#endif // OS_VERSION_GENERIC < 42200 || OS_VERSION_GENERIC > 50600
|
|
|
|
if ((thread.Stat & 0x3) != 0) {
|
|
PW_LOG_DEBUG("Thread state: SUSPENDED");
|
|
encoder.WriteState(ThreadState::Enum::SUSPENDED);
|
|
} else if ((thread.Stat & 0xf8) == 0) {
|
|
PW_LOG_DEBUG("Thread state: READY");
|
|
encoder.WriteState(ThreadState::Enum::READY);
|
|
} else {
|
|
PW_LOG_DEBUG("Thread state: BLOCKED");
|
|
encoder.WriteState(ThreadState::Enum::BLOCKED);
|
|
}
|
|
}
|
|
|
|
} // 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 OS_TASK& 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() && !status.IsFailedPrecondition()) {
|
|
PW_LOG_ERROR("Failed to iterate threads during snapshot capture: %d",
|
|
static_cast<int>(status.code()));
|
|
}
|
|
|
|
return ctx.thread_capture_status;
|
|
}
|
|
|
|
Status SnapshotThread(const OS_TASK& thread,
|
|
void* running_thread_stack_pointer,
|
|
Thread::StreamEncoder& encoder,
|
|
ProcessThreadStackCallback& thread_stack_callback) {
|
|
#if OS_TRACKNAME
|
|
PW_LOG_DEBUG("Capturing thread info for %s", thread.Name);
|
|
encoder.WriteName(std::as_bytes(std::span(std::string_view(thread.Name))));
|
|
#else
|
|
PW_LOG_DEBUG("Capturing thread info for thread at 0x%08x", &thread);
|
|
#endif // OS_TRACKNAME
|
|
|
|
CaptureThreadState(thread, encoder);
|
|
|
|
#if OS_CHECKSTACK || OS_SUPPORT_MPU
|
|
const StackContext thread_ctx = {
|
|
.thread_name = thread.Name,
|
|
|
|
.stack_low_addr = reinterpret_cast<uintptr_t>(thread.pStackBot),
|
|
|
|
.stack_high_addr =
|
|
reinterpret_cast<uintptr_t>(thread.pStackBot) + thread.StackSize,
|
|
|
|
// 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.pStack),
|
|
.stack_pointer_est_peak = reinterpret_cast<uintptr_t>(thread.pStackBot) +
|
|
thread.StackSize - OS_GetStackUsed(&thread),
|
|
};
|
|
|
|
return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
|
|
#else
|
|
PW_LOG_DEBUG("Stack pointer: 0x%08x", running_thread_stack_pointer);
|
|
encoder.WriteStackPointer(reinterpret_cast<uintptr_t>(
|
|
ThreadIsRunning(thread) ? running_thread_stack_pointer : thread.pStack));
|
|
return encoder.status();
|
|
#endif // OS_CHECKSTACK || OS_SUPPORT_MPU
|
|
}
|
|
|
|
} // namespace pw::thread::embos
|