| // 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 <err.h> |
| #include <platform.h> |
| #include <string.h> |
| |
| #include <arch/ops.h> |
| #include <arch/user_copy.h> |
| #include <kernel/cmdline.h> |
| #include <vm/vm_aspace.h> |
| #include <lib/ktrace.h> |
| #include <lk/init.h> |
| #include <zircon/thread_annotations.h> |
| #include <object/thread_dispatcher.h> |
| |
| #define ktrace_timestamp() current_ticks(); |
| #define ktrace_ticks_per_ms() (ticks_per_second() / 1000) |
| |
| static void ktrace_name_etc(uint32_t tag, uint32_t id, uint32_t arg, const char* name, bool always); |
| |
| // Generated struct that has the syscall index and name. |
| static struct ktrace_syscall_info { |
| uint32_t id; |
| uint32_t nargs; |
| const char* name; |
| } kt_syscall_info [] = { |
| #include <zircon/syscall-ktrace-info.inc> |
| {0, 0, nullptr} |
| }; |
| |
| void ktrace_report_syscalls(ktrace_syscall_info* call) { |
| size_t ix = 0; |
| while (call[ix].name) { |
| ktrace_name_etc(TAG_SYSCALL_NAME, call[ix].id, 0, call[ix].name, true); |
| ++ix; |
| } |
| } |
| |
| static uint32_t probe_number = 1; |
| |
| extern ktrace_probe_info_t* const __start_ktrace_probe[]; |
| extern ktrace_probe_info_t* const __stop_ktrace_probe[]; |
| |
| static fbl::Mutex probe_list_lock; |
| static ktrace_probe_info_t* probe_list TA_GUARDED(probe_list_lock); |
| |
| static ktrace_probe_info_t* ktrace_find_probe(const char* name) TA_REQ(probe_list_lock) { |
| ktrace_probe_info_t* probe; |
| for (probe = probe_list; probe != nullptr; probe = probe->next) { |
| if (!strcmp(name, probe->name)) { |
| return probe; |
| } |
| } |
| return nullptr; |
| } |
| |
| static void ktrace_add_probe(ktrace_probe_info_t* probe) TA_REQ(probe_list_lock) { |
| if (probe->num == 0) { |
| probe->num = probe_number++; |
| } |
| probe->next = probe_list; |
| probe_list = probe; |
| ktrace_name_etc(TAG_PROBE_NAME, probe->num, 0, probe->name, true); |
| } |
| |
| static void ktrace_report_probes(void) { |
| fbl::AutoLock lock(&probe_list_lock); |
| ktrace_probe_info_t *probe; |
| for (probe = probe_list; probe != nullptr; probe = probe->next) { |
| ktrace_name_etc(TAG_PROBE_NAME, probe->num, 0, probe->name, true); |
| } |
| } |
| |
| typedef struct ktrace_state { |
| // where the next record will be written |
| int offset; |
| |
| // mask of groups we allow, 0 == tracing disabled |
| int grpmask; |
| |
| // total size of the trace buffer |
| uint32_t bufsize; |
| |
| // offset where tracing was stopped, 0 if tracing active |
| uint32_t marker; |
| |
| // raw trace buffer |
| uint8_t* buffer; |
| } ktrace_state_t; |
| |
| static ktrace_state_t KTRACE_STATE; |
| |
| int ktrace_read_user(void* ptr, uint32_t off, uint32_t len) { |
| ktrace_state_t* ks = &KTRACE_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 (ks->marker) { |
| max = ks->marker; |
| } else { |
| max = ks->offset; |
| if (max > ks->bufsize) { |
| max = ks->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, ks->buffer + off, len) != ZX_OK) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return len; |
| } |
| |
| zx_status_t ktrace_control(uint32_t action, uint32_t options, void* ptr) { |
| ktrace_state_t* ks = &KTRACE_STATE; |
| switch (action) { |
| case KTRACE_ACTION_START: |
| options = KTRACE_GRP_TO_MASK(options); |
| ks->marker = 0; |
| atomic_store(&ks->grpmask, options ? options : KTRACE_GRP_TO_MASK(KTRACE_GRP_ALL)); |
| ktrace_report_live_processes(); |
| ktrace_report_live_threads(); |
| break; |
| case KTRACE_ACTION_STOP: { |
| atomic_store(&ks->grpmask, 0); |
| uint32_t n = ks->offset; |
| if (n > ks->bufsize) { |
| ks->marker = ks->bufsize; |
| } else { |
| ks->marker = n; |
| } |
| break; |
| } |
| case KTRACE_ACTION_REWIND: |
| // roll back to just after the metadata |
| atomic_store(&ks->offset, KTRACE_RECSIZE * 2); |
| ktrace_report_syscalls(kt_syscall_info); |
| ktrace_report_probes(); |
| break; |
| case KTRACE_ACTION_NEW_PROBE: { |
| fbl::AutoLock lock(&probe_list_lock); |
| ktrace_probe_info_t* probe; |
| if ((probe = ktrace_find_probe((const char*) ptr)) != nullptr) { |
| return probe->num; |
| } |
| probe = (ktrace_probe_info_t*) calloc(sizeof(*probe) + ZX_MAX_NAME_LEN, 1); |
| if (probe == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| probe->name = (const char*) (probe + 1); |
| memcpy(probe + 1, ptr, ZX_MAX_NAME_LEN); |
| ktrace_add_probe(probe); |
| return probe->num; |
| } |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| int trace_not_ready = 0; |
| |
| void ktrace_init(unsigned level) { |
| ktrace_state_t* ks = &KTRACE_STATE; |
| |
| uint32_t mb = cmdline_get_uint32("ktrace.bufsize", KTRACE_DEFAULT_BUFSIZE); |
| uint32_t grpmask = cmdline_get_uint32("ktrace.grpmask", KTRACE_DEFAULT_GRPMASK); |
| |
| if (mb == 0) { |
| dprintf(INFO, "ktrace: disabled\n"); |
| return; |
| } |
| |
| mb *= (1024*1024); |
| |
| zx_status_t status; |
| VmAspace* aspace = VmAspace::kernel_aspace(); |
| if ((status = aspace->Alloc("ktrace", mb, (void**)&ks->buffer, 0, VmAspace::VMM_FLAG_COMMIT, |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE)) < 0) { |
| dprintf(INFO, "ktrace: cannot alloc buffer %d\n", status); |
| return; |
| } |
| |
| // The last packet written can overhang the end of the buffer, |
| // so we reduce the reported size by the max size of a record |
| ks->bufsize = mb - 256; |
| |
| dprintf(INFO, "ktrace: buffer at %p (%u bytes)\n", ks->buffer, mb); |
| |
| // register all static probes |
| { |
| fbl::AutoLock lock(&probe_list_lock); |
| for (auto probe = __start_ktrace_probe; |
| probe != __stop_ktrace_probe; |
| ++probe) { |
| ktrace_add_probe(*probe); |
| } |
| } |
| |
| // write metadata to the first two event slots |
| uint64_t n = ktrace_ticks_per_ms(); |
| ktrace_rec_32b_t* rec = (ktrace_rec_32b_t*) ks->buffer; |
| rec[0].tag = TAG_VERSION; |
| rec[0].a = KTRACE_VERSION; |
| rec[1].tag = TAG_TICKS_PER_MS; |
| rec[1].a = (uint32_t)n; |
| rec[1].b = (uint32_t)(n >> 32); |
| |
| // enable tracing |
| atomic_store(&ks->offset, KTRACE_RECSIZE * 2); |
| ktrace_report_syscalls(kt_syscall_info); |
| ktrace_report_probes(); |
| atomic_store(&ks->grpmask, KTRACE_GRP_TO_MASK(grpmask)); |
| |
| // report names of existing threads |
| ktrace_report_live_threads(); |
| |
| // 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_probe0("ktrace_ready"); |
| } |
| |
| void ktrace_tiny(uint32_t tag, uint32_t arg) { |
| ktrace_state_t* ks = &KTRACE_STATE; |
| if (tag & atomic_load(&ks->grpmask)) { |
| tag = (tag & 0xFFFFFFF0) | 2; |
| int off; |
| if ((off = atomic_add(&ks->offset, KTRACE_HDRSIZE)) >= (int)ks->bufsize) { |
| // if we arrive at the end, stop |
| atomic_store(&ks->grpmask, 0); |
| } else { |
| ktrace_header_t* hdr = (ktrace_header_t*) (ks->buffer + off); |
| hdr->ts = ktrace_timestamp(); |
| hdr->tag = tag; |
| hdr->tid = arg; |
| } |
| } |
| } |
| |
| void* ktrace_open(uint32_t tag) { |
| ktrace_state_t* ks = &KTRACE_STATE; |
| if (!(tag & atomic_load(&ks->grpmask))) { |
| return nullptr; |
| } |
| |
| int off; |
| if ((off = atomic_add(&ks->offset, KTRACE_LEN(tag))) >= (int)ks->bufsize) { |
| // if we arrive at the end, stop |
| atomic_store(&ks->grpmask, 0); |
| return nullptr; |
| } |
| |
| ktrace_header_t* hdr = (ktrace_header_t*) (ks->buffer + off); |
| hdr->ts = ktrace_timestamp(); |
| hdr->tag = tag; |
| hdr->tid = (uint32_t)get_current_thread()->user_tid; |
| return hdr + 1; |
| } |
| |
| static void ktrace_name_etc(uint32_t tag, uint32_t id, uint32_t arg, const char* name, bool always) { |
| ktrace_state_t* ks = &KTRACE_STATE; |
| if ((tag & atomic_load(&ks->grpmask)) || always) { |
| 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); |
| |
| int off; |
| if ((off = atomic_add(&ks->offset, KTRACE_LEN(tag))) >= (int)ks->bufsize) { |
| // if we arrive at the end, stop |
| atomic_store(&ks->grpmask, 0); |
| } else { |
| ktrace_rec_name_t* rec = (ktrace_rec_name_t*) (ks->buffer + off); |
| rec->tag = tag; |
| rec->id = id; |
| rec->arg = arg; |
| memcpy(rec->name, name, len); |
| rec->name[len] = 0; |
| } |
| } |
| } |
| |
| void ktrace_name(uint32_t tag, uint32_t id, uint32_t arg, const char* name) { |
| ktrace_name_etc(tag, id, arg, name, false); |
| } |
| |
| LK_INIT_HOOK(ktrace, ktrace_init, LK_INIT_LEVEL_USER); |