blob: d85642ea61a996e35d1318651a68884ddca0ac24 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <debug.h>
#include <lib/boot-options/boot-options.h>
#include <lib/ktrace.h>
#include <lib/ktrace/string_ref.h>
#include <lib/syscalls/zx-syscall-numbers.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <platform.h>
#include <string.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <arch/ops.h>
#include <arch/user_copy.h>
#include <fbl/alloc_checker.h>
#include <hypervisor/ktrace.h>
#include <ktl/atomic.h>
#include <ktl/iterator.h>
#include <lk/init.h>
#include <object/thread_dispatcher.h>
#include <vm/vm_aspace.h>
// The global ktrace state.
internal::KTraceState KTRACE_STATE;
namespace {
// One of these macros is invoked by kernel.inc for each syscall.
// These don't have kernel entry points.
#define VDSO_SYSCALL(...)
// These are the direct kernel entry points.
#define KERNEL_SYSCALL(name, type, attrs, nargs, arglist, prototype) [ZX_SYS_##name] = #name,
#define INTERNAL_SYSCALL(...) KERNEL_SYSCALL(__VA_ARGS__)
#define BLOCKING_SYSCALL(...) KERNEL_SYSCALL(__VA_ARGS__)
#if defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc99-designator"
#endif
constexpr const char* kSyscallNames[] = {
#include <lib/syscalls/kernel.inc>
};
#if defined(__clang__)
#pragma GCC diagnostic pop
#endif
#undef VDSO_SYSCALL
#undef KERNEL_SYSCALL
#undef INTERNAL_SYSCALL
#undef BLOCKING_SYSCALL
void ktrace_report_syscalls() {
for (uint32_t i = 0; i < ktl::size(kSyscallNames); ++i) {
ktrace_name_etc(TAG_SYSCALL_NAME, i, 0, kSyscallNames[i], true);
}
}
zx_ticks_t ktrace_ticks_per_ms() { return ticks_per_second() / 1000; }
StringRef* ktrace_find_probe(const char* name) {
for (StringRef* ref = StringRef::head(); ref != nullptr; ref = ref->next) {
if (!strcmp(name, ref->string)) {
return ref;
}
}
return nullptr;
}
void ktrace_add_probe(StringRef* string_ref) {
// Register and emit the string ref.
string_ref->GetId();
}
void ktrace_report_probes() {
for (StringRef* ref = StringRef::head(); ref != nullptr; ref = ref->next) {
ktrace_name_etc(TAG_PROBE_NAME, ref->id, 0, ref->string, true);
}
}
} // namespace
namespace internal {
KTraceState::~KTraceState() {
if (buffer_ != nullptr) {
VmAspace* aspace = VmAspace::kernel_aspace();
aspace->FreeRegion(reinterpret_cast<vaddr_t>(buffer_));
}
}
void KTraceState::Init(uint32_t target_bufsize, uint32_t initial_groups) {
Guard<Mutex> guard(&lock_);
ASSERT_MSG(target_bufsize_ == 0,
"Double init of KTraceState instance (tgt_bs %u, new tgt_bs %u)!", target_bufsize_,
target_bufsize);
ASSERT(is_started_ == false);
target_bufsize_ = target_bufsize;
if (initial_groups != 0) {
if (AllocBuffer() == ZX_OK) {
ReportThreadProcessNames();
is_started_ = true;
}
}
grpmask_.store(KTRACE_GRP_TO_MASK(initial_groups));
}
zx_status_t KTraceState::Start(uint32_t groups) {
Guard<Mutex> guard(&lock_);
if (groups == 0) {
return ZX_ERR_INVALID_ARGS;
}
if (zx_status_t status = AllocBuffer(); status != ZX_OK) {
return status;
}
// If we are not yet started, we need to report the current thread and process
// names, and update our and buffer bookkeeping. If we are already started,
// then this request to start should be treated simply as an update of the
// group mask.
if (!is_started_) {
marker_ = 0;
ReportThreadProcessNames();
is_started_ = true;
}
grpmask_.store(KTRACE_GRP_TO_MASK(groups));
return ZX_OK;
}
zx_status_t KTraceState::Stop() {
Guard<Mutex> guard(&lock_);
// Stopping an already stopped KTrace is OK, the operation is idempotent.
if (!is_started_) {
return ZX_OK;
}
grpmask_.store(0);
uint32_t n = offset_.load();
if (n > bufsize_) {
marker_ = bufsize_;
} else {
marker_ = n;
}
is_started_ = false;
return ZX_OK;
}
zx_status_t KTraceState::RewindLocked() {
if (is_started_) {
return ZX_ERR_BAD_STATE;
}
// roll back to just after the metadata
offset_.store(KTRACE_RECSIZE * 2);
buffer_full_.store(false);
ReportStaticNames();
return ZX_OK;
}
ssize_t KTraceState::ReadUser(void* ptr, uint32_t off, size_t len) {
Guard<Mutex> guard(&lock_);
if (is_started_) {
return ZX_ERR_BAD_STATE;
}
// Buffer size is limited by the marker if set,
// otherwise limited by offset (last written point).
// Offset can end up pointing past the end, so clip
// it to the actual buffer size to be safe.
uint32_t max;
if (marker_) {
max = marker_;
} else {
max = offset_.load();
if (max > bufsize_) {
max = bufsize_;
}
}
// null read is a query for trace buffer size
if (ptr == nullptr) {
return max;
}
// constrain read to available buffer
if (off >= max) {
return 0;
}
if (len > (max - off)) {
len = max - off;
}
if (arch_copy_to_user(ptr, buffer_ + off, len) != ZX_OK) {
return ZX_ERR_INVALID_ARGS;
}
return len;
}
// Write out a ktrace record with no payload.
template <>
void KTraceState::WriteRecord(uint32_t effective_tag, uint64_t explicit_ts) {
DEBUG_ASSERT(KTRACE_LEN(effective_tag) >= sizeof(ktrace_header_t));
if (explicit_ts == kRecordCurrentTimestamp) {
explicit_ts = ktrace_timestamp();
}
Open(effective_tag, explicit_ts);
}
// Write out a ktrace record with the given arguments as a payload.
//
// Arguments must be of the same type.
template <typename... Args>
void KTraceState::WriteRecord(uint32_t effective_tag, uint64_t explicit_ts, Args... args) {
DEBUG_ASSERT(KTRACE_LEN(effective_tag) >= (sizeof(ktrace_header_t) + sizeof...(Args)));
if (explicit_ts == kRecordCurrentTimestamp) {
explicit_ts = ktrace_timestamp();
}
// Write out each arg.
auto payload = {args...};
using PayloadType = typename decltype(payload)::value_type;
if (auto* data = static_cast<PayloadType*>(Open(effective_tag, explicit_ts)); data != nullptr) {
int i = 0;
for (auto arg : payload) {
data[i++] = arg;
}
}
}
void KTraceState::WriteRecordTiny(uint32_t tag, uint32_t arg) {
tag = (tag & 0xFFFFFFF0) | 2;
uint32_t off;
if ((off = (offset_.fetch_add(KTRACE_HDRSIZE))) >= (bufsize_)) {
// if we arrive at the end, stop
Disable();
} else {
ktrace_header_t* hdr = reinterpret_cast<ktrace_header_t*>(buffer_ + off);
hdr->ts = ktrace_timestamp();
hdr->tag = tag;
hdr->tid = arg;
}
}
void KTraceState::WriteNameEtc(uint32_t tag, uint32_t id, uint32_t arg, const char* name,
bool always) {
if (tag_enabled(tag) || (always && !buffer_full_.load())) {
const uint32_t len = static_cast<uint32_t>(strnlen(name, ZX_MAX_NAME_LEN - 1));
// set size to: sizeof(hdr) + len + 1, round up to multiple of 8
tag = (tag & 0xFFFFFFF0) | ((KTRACE_NAMESIZE + len + 1 + 7) >> 3);
uint32_t off;
if ((off = offset_.fetch_add(KTRACE_LEN(tag))) >= bufsize_) {
// if we arrive at the end, stop
Disable();
} else {
ktrace_rec_name_t* rec = reinterpret_cast<ktrace_rec_name_t*>(buffer_ + off);
rec->tag = tag;
rec->id = id;
rec->arg = arg;
memcpy(rec->name, name, len);
rec->name[len] = 0;
}
}
}
void KTraceState::ReportStaticNames() {
ktrace_report_syscalls();
ktrace_report_probes();
ktrace_report_vcpu_meta();
}
void KTraceState::ReportThreadProcessNames() {
ktrace_report_live_processes();
ktrace_report_live_threads();
}
zx_status_t KTraceState::AllocBuffer() {
// The buffer is allocated once, then never deleted. If it has already been
// allocated, then we are done.
if (buffer_) {
return ZX_OK;
}
// We require that our buffer be a multiple of page size, and non-zero. If
// the target buffer size ends up being zero, it is most likely because boot
// args set the buffer size to zero. For now, report NOT_SUPPORTED up the
// stack to signal to usermode tracing (hitting AllocBuffer via Start) that
// ktracing has been disabled.
target_bufsize_ = static_cast<uint32_t>(target_bufsize_ & ~(PAGE_SIZE - 1));
if (!target_bufsize_) {
return ZX_ERR_NOT_SUPPORTED;
}
DEBUG_ASSERT(is_started_ == false);
zx_status_t status;
VmAspace* aspace = VmAspace::kernel_aspace();
if ((status = aspace->Alloc("ktrace", target_bufsize_, reinterpret_cast<void**>(&buffer_), 0,
VmAspace::VMM_FLAG_COMMIT,
ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE)) < 0) {
DiagsPrintf(INFO, "ktrace: cannot alloc buffer %d\n", status);
return ZX_ERR_NO_MEMORY;
}
// The last packet written can overhang the end of the buffer,
// so we reduce the reported size by the max size of a record
bufsize_ = target_bufsize_ - 256;
DiagsPrintf(INFO, "ktrace: buffer at %p (%u bytes)\n", buffer_, target_bufsize_);
// write metadata to the first two event slots
uint64_t n = ktrace_ticks_per_ms();
ktrace_rec_32b_t* rec = reinterpret_cast<ktrace_rec_32b_t*>(buffer_);
rec[0].tag = TAG_VERSION;
rec[0].a = KTRACE_VERSION;
rec[1].tag = TAG_TICKS_PER_MS;
rec[1].a = static_cast<uint32_t>(n);
rec[1].b = static_cast<uint32_t>(n >> 32);
[[maybe_unused]] zx_status_t rewind_res = RewindLocked();
DEBUG_ASSERT(rewind_res == ZX_OK);
// Report an event for "tracing is all set up now". This also
// serves to ensure that there will be at least one static probe
// entry so that the __{start,stop}_ktrace_probe symbols above
// will be defined by the linker.
ktrace_probe(TraceAlways, TraceContext::Thread, "ktrace_ready"_stringref);
return ZX_OK;
}
void* KTraceState::Open(uint32_t tag, uint64_t ts) {
uint32_t off;
if ((off = offset_.fetch_add(KTRACE_LEN(tag))) >= bufsize_) {
// if we arrive at the end, stop
Disable();
return nullptr;
}
ktrace_header_t* hdr = reinterpret_cast<ktrace_header_t*>(buffer_ + off);
hdr->ts = ts;
hdr->tag = tag;
hdr->tid = KTRACE_FLAGS(tag) & KTRACE_FLAGS_CPU
? arch_curr_cpu_num()
: static_cast<uint32_t>(Thread::Current::Get()->tid());
return hdr + 1;
}
// Instantiate used versions of |KTraceState::WriteRecord|.
template void KTraceState::WriteRecord(uint32_t effective_tag, uint64_t explicit_ts, uint32_t a);
template void KTraceState::WriteRecord(uint32_t effective_tag, uint64_t explicit_ts, uint32_t a,
uint32_t b);
template void KTraceState::WriteRecord(uint32_t effective_tag, uint64_t explicit_ts, uint32_t a,
uint32_t b, uint32_t c);
template void KTraceState::WriteRecord(uint32_t effective_tag, uint64_t explicit_ts, uint32_t a,
uint32_t b, uint32_t c, uint32_t d);
template void KTraceState::WriteRecord(uint32_t effective_tag, uint64_t explicit_ts, uint64_t a);
template void KTraceState::WriteRecord(uint32_t effective_tag, uint64_t explicit_ts, uint64_t a,
uint64_t b);
} // namespace internal
zx_status_t ktrace_control(uint32_t action, uint32_t options, void* ptr) {
switch (action) {
case KTRACE_ACTION_START:
return KTRACE_STATE.Start(options ? options : KTRACE_GRP_ALL);
case KTRACE_ACTION_STOP:
return KTRACE_STATE.Stop();
case KTRACE_ACTION_REWIND:
return KTRACE_STATE.Rewind();
case KTRACE_ACTION_NEW_PROBE: {
const char* const string_in = static_cast<const char*>(ptr);
StringRef* ref = ktrace_find_probe(string_in);
if (ref != nullptr) {
return ref->id;
}
struct DynamicStringRef {
DynamicStringRef(const char* string) : string_ref{storage} {
memcpy(storage, string, sizeof(storage));
}
StringRef string_ref;
char storage[ZX_MAX_NAME_LEN];
};
// TODO(eieio,dje): Figure out how to constrain this to prevent abuse by
// creating huge numbers of unique probes.
fbl::AllocChecker alloc_checker;
DynamicStringRef* dynamic_ref = new (&alloc_checker) DynamicStringRef{string_in};
if (!alloc_checker.check()) {
return ZX_ERR_NO_MEMORY;
}
ktrace_add_probe(&dynamic_ref->string_ref);
return dynamic_ref->string_ref.id;
}
default:
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
void ktrace_init(unsigned level) {
// There's no utility in setting up the singleton ktrace instance if there are
// no syscalls to access it. See zircon/kernel/syscalls/debug.cc for the
// corresponding syscalls. Note that because KTRACE_STATE grpmask starts at 0
// and will not be changed, the other functions in this file need not check
// for enabled-ness manually.
const bool syscalls_enabled = gBootOptions->enable_debugging_syscalls;
const uint32_t bufsize = syscalls_enabled ? (gBootOptions->ktrace_bufsize << 20) : 0;
const uint32_t initial_grpmask = gBootOptions->ktrace_grpmask;
if (!bufsize) {
dprintf(INFO, "ktrace: disabled\n");
return;
}
KTRACE_STATE.Init(bufsize, initial_grpmask);
if (!initial_grpmask) {
dprintf(INFO, "ktrace: delaying buffer allocation\n");
}
}
// Finish initialization before starting userspace (i.e. before debug syscalls can occur).
LK_INIT_HOOK(ktrace, ktrace_init, LK_INIT_LEVEL_USER - 1)